diff --git a/.github/workflows/EssentialsPlugins-builds-4-series-caller.yml b/.github/workflows/EssentialsPlugins-builds-4-series-caller.yml index 121e6cf9..d5990e99 100644 --- a/.github/workflows/EssentialsPlugins-builds-4-series-caller.yml +++ b/.github/workflows/EssentialsPlugins-builds-4-series-caller.yml @@ -6,13 +6,9 @@ on: - '**' jobs: - runTests: - uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-run-tests.yml@main - secrets: inherit getVersion: uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main secrets: inherit - needs: runTests build-4Series: uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main secrets: inherit diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 35752ff7..42dd64c5 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -26,9 +26,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Dotnet Setup - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v4 with: dotnet-version: 8.x diff --git a/PepperDash.Essentials.4Series.sln b/PepperDash.Essentials.4Series.sln index 0bc995c0..7423c50a 100644 --- a/PepperDash.Essentials.4Series.sln +++ b/PepperDash.Essentials.4Series.sln @@ -36,189 +36,49 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core.Tests", "src\PepperDash.Core.Tests\PepperDash.Core.Tests.csproj", "{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.Core.Tests", "src\PepperDash.Essentials.Core.Tests\PepperDash.Essentials.Core.Tests.csproj", "{F508E0BA-E885-424F-9D4C-359CF0011DEF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.Tests", "src\PepperDash.Essentials.Tests\PepperDash.Essentials.Tests.csproj", "{841EE676-7784-4456-8F76-3697C6D432A6}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU - Debug 4.7.2|x64 = Debug 4.7.2|x64 - Debug 4.7.2|x86 = Debug 4.7.2|x86 Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x64.ActiveCfg = Debug|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x64.Build.0 = Debug|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x86.ActiveCfg = Debug|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x86.Build.0 = Debug|Any CPU {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.ActiveCfg = Release|Any CPU {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = Release|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x64.ActiveCfg = Release|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x64.Build.0 = Release|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x86.ActiveCfg = Release|Any CPU - {53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x86.Build.0 = Release|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x64.ActiveCfg = Debug|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x64.Build.0 = Debug|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x86.ActiveCfg = Debug|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x86.Build.0 = Debug|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = Release|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x64.ActiveCfg = Release|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x64.Build.0 = Release|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x86.ActiveCfg = Release|Any CPU - {CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x86.Build.0 = Release|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x64.ActiveCfg = Debug|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x64.Build.0 = Debug|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x86.ActiveCfg = Debug|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x86.Build.0 = Debug|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = Release|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x64.ActiveCfg = Release|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x64.Build.0 = Release|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x86.ActiveCfg = Release|Any CPU - {3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x86.Build.0 = Release|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x64.ActiveCfg = Debug|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x64.Build.0 = Debug|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x86.ActiveCfg = Debug|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x86.Build.0 = Debug|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.Build.0 = Release|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x64.ActiveCfg = Release|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x64.Build.0 = Release|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x86.ActiveCfg = Release|Any CPU - {F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x86.Build.0 = Release|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x64.ActiveCfg = Debug|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x64.Build.0 = Debug|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x86.ActiveCfg = Debug|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x86.Build.0 = Debug|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.Build.0 = Release|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Release|x64.ActiveCfg = Release|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Release|x64.Build.0 = Release|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Release|x86.ActiveCfg = Release|Any CPU - {B438694F-8FF7-464A-9EC8-10427374471F}.Release|x86.Build.0 = Release|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x64.ActiveCfg = Debug|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x64.Build.0 = Debug|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x86.ActiveCfg = Debug|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x86.Build.0 = Debug|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x64.ActiveCfg = Release|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x64.Build.0 = Release|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.ActiveCfg = Release|Any CPU - {E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.Build.0 = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x64.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x86.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x64.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x64.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x86.ActiveCfg = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x86.Build.0 = Debug|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|Any CPU.Build.0 = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x64.ActiveCfg = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x64.Build.0 = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x86.ActiveCfg = Release|Any CPU - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x86.Build.0 = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x64.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x86.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x64.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x64.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x86.ActiveCfg = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x86.Build.0 = Debug|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|Any CPU.Build.0 = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x64.ActiveCfg = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x64.Build.0 = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x86.ActiveCfg = Release|Any CPU - {F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x86.Build.0 = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|x64.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug 4.7.2|x86.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|x64.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|x64.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|x86.ActiveCfg = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Debug|x86.Build.0 = Debug|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|Any CPU.Build.0 = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|x64.ActiveCfg = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|x64.Build.0 = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|x86.ActiveCfg = Release|Any CPU - {841EE676-7784-4456-8F76-3697C6D432A6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -230,9 +90,6 @@ Global {F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206} {B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206} {E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {680BA287-E61F-4B8D-BD7A-84C2504F5F9C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {F508E0BA-E885-424F-9D4C-359CF0011DEF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {841EE676-7784-4456-8F76-3697C6D432A6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3} diff --git a/README.md b/README.md index fac87250..b485eade 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Utilization of Essentials Framework falls into the following categories: ## Documentation -For detailed documentation, see the [DocFx Site](https://pepperdash.github.io/Essentials/). +For detailed documentation, see the [Wiki](https://github.com/PepperDash/EssentialsFramework/wiki). ## Support diff --git a/docs/docs/Home.md b/docs/docs/Home.md index bdda2185..8e0c163e 100644 --- a/docs/docs/Home.md +++ b/docs/docs/Home.md @@ -19,13 +19,12 @@ Or use the links to the right to navigate our documentation. ## Benefits -- Runs on Crestron **4-Series** and VC-4 Control System platforms +- Runs on Crestron 3-Series, **4-Series** and VC-4 Control System platforms - Reduced hardware overhead compared to S+ and Simpl solutions - Quick development cycle - Shared resources made easily available - More flexibility with less code - Configurable using simple JSON files -- Built in websocket communication API for HTML5 frontend applications. - Is awesome --- diff --git a/docs/docs/technical-docs/Arch-activate.md b/docs/docs/technical-docs/Arch-activate.md index 61a0b68b..72a9e3eb 100644 --- a/docs/docs/technical-docs/Arch-activate.md +++ b/docs/docs/technical-docs/Arch-activate.md @@ -32,7 +32,7 @@ If the `CustomActivate()` method is long, consider breaking it up into many smal Note: It is best-practice in Essentials to not write arbitrarily-timed startup sequences to ensure that a "system" or room is functional. Rather, we encourage the developer to use various properties and conditions on devices to aggregate together "room is ready" statuses that can trigger further action. This ensures that all devices can be up and alive, allowing them to be debugged within a program that may otherwise be misbehaving - as well as not making users and expensive developers wait for code to start up! ```cs -protected override bool CustomActivate() +public override bool CustomActivate() { Debug.Console(0, this, "Final activation. Setting up actions and feedbacks"); SetupFunctions(); @@ -52,7 +52,7 @@ We can see in the example below that during the `CustomActivate()` phase, we def ### **Example** ```cs -protected override bool CustomActivate() +public override bool CustomActivate() { foreach (var i in Config.Inputs) { @@ -115,7 +115,7 @@ The main task that should be undertaken in the `Initialize()` method for any 3rd ### Example (from `PepperDash.Essentials.Devices.Common.VideoCodec.Cisco.CiscoSparkCodec`) ```cs - protected override void Initialize() + public override void Initialize() { var socket = Communication as ISocketStatus; if (socket != null) diff --git a/docs/docs/technical-docs/Connection-Based-Routing.md b/docs/docs/technical-docs/Connection-Based-Routing.md index f5ff3be0..07dc318b 100644 --- a/docs/docs/technical-docs/Connection-Based-Routing.md +++ b/docs/docs/technical-docs/Connection-Based-Routing.md @@ -2,42 +2,58 @@ ## TL;DR -Routing is defined by a connection graph or a wiring diagram. Routeable devices are sources, midpoints, or destinations. Devices are connected by tie lines. Tie lines represent the cables connecting devices, and are of type audio, video or both. Routes are made by telling a destination to get an audio/video/combined route from a source. +Routing is defined by a connection graph or a wiring diagram. Routable devices are sources, midpoints, or destinations. Devices are connected by tie lines. Tie lines represent the cables connecting devices, and have specific signal types (audio, video, audioVideo, secondaryAudio, usbInput, usbOutput). Routes are made by telling a destination to get a route from a source for a specific signal type. Combined signal types (e.g., audioVideo) are automatically split into separate routing operations. ## Summary Essentials routing is described by defining a graph of connections between devices in a system, typically in configuration. The audio, video and combination connections are like a wiring diagram. This graph is a collection of devices and tie lines, each tie line connecting a source device, source output port, destination device and destination input port. Tie lines are logically represented as a collection. -When routes are to be executed, Essentials will use this connection graph to decide on routes from source to destination. A method call is made on a destination, which says “destination, find a way for source xyz to get to you.” An algorithm analyzes the tie lines, instantly walking backwards from the destination, down every connection until it finds a complete path from the source. If a connected path is found, the algorithm then walks forward through all midpoints to the destination, executing switches as required until the full route is complete. The developer or configurer only needs to say “destination, get source xyz” and Essentials figures out how, regardless of what devices lie in between. +When routes are to be executed, Essentials will use this connection graph to decide on routes from source to destination. A method call is made on a destination, which says "destination, find a way for source xyz to get to you." An algorithm analyzes the tie lines, instantly walking backwards from the destination, down every connection until it finds a complete path from the source. If a connected path is found, the algorithm then walks forward through all midpoints to the destination, executing switches as required until the full route is complete. The developer or configurer only needs to say "destination, get source xyz" and Essentials figures out how, regardless of what devices lie in between. + +### Signal Type Handling + +When a combined signal type like `audioVideo` is requested, Essentials automatically splits it into two separate routing operations—one for audio and one for video. Each signal type is routed independently through the system, ensuring that: +- Audio-only tie lines can be used for the audio portion +- Video-only tie lines can be used for the video portion +- AudioVideo tie lines can be used for both portions + +During path discovery, **only tie lines that support the requested signal type are considered**. For example, if a video route is requested, only tie lines with the video flag will be evaluated. This ensures signal compatibility throughout the entire routing chain. + +### Port-Specific Routing + +The routing system supports routing to and from specific ports on devices. You can specify: +- A specific input port on the destination device +- A specific output port on the source device +- Both specific ports for precise routing control + +When no specific ports are specified, the algorithm will automatically discover the appropriate ports based on available tie lines. + +### Request Queuing + +All routing requests are processed sequentially through a queue. For devices that implement warming/cooling behavior (e.g., projectors), route requests are automatically held when a device is cooling down and executed once the device is ready. This prevents routing errors and ensures proper device state management. ### Classes Referenced -* `PepperDash.Essentials.Core.IRoutingSource` -* `PepperDash.Essentials.Core.IRoutingOutputs` -* `PepperDash.Essentials.Core.IRoutingInputs` -* `PepperDash.Essentials.Core.IRoutingMidpoint` -* `PepperDash.Essentials.Core.IRoutingMidpointWithFeedback` -* `PepperDash.Essentials.Core.IRoutingSinkWithFeedback` - -> **Note:** As of the v3 routing refactor, the older `IRoutingInputsOutputs`, `IRouting`, -> `IRoutingWithFeedback`, `IRoutingWithClear`, `IRoutingSink`, `IRoutingSinkWithInputPort`, -> `IRoutingSinkNoSwitching`, `IRoutingSinkWithSwitching`, and `IRoutingSinkWithSwitchingWithInputPort` -> interfaces have been removed and consolidated - see [Routing interfaces (v3)](#routing-interfaces-v3) -> below for the current mapping. +* `PepperDash.Essentials.Core.Routing.IRoutingSource` +* `PepperDash.Essentials.Core.Routing.IRoutingOutputs` +* `PepperDash.Essentials.Core.Routing.IRoutingInputs` +* `PepperDash.Essentials.Core.Routing.IRoutingInputsOutputs` +* `PepperDash.Essentials.Core.Routing.IRoutingSinkNoSwitching` +* `PepperDash.Essentials.Core.Routing.IRoutingSinkWithSwitching` ## Example system, a simple presentation system The diagram below shows the connections in a simple presentation system, with a few variations in connection paths. Example routes will be described following the diagram. -Each visible line between ports on devices represents a tie line. A tie line connects an output port on one device to an input port on another device, for example: an HDMI port on a document camera to an HDMI input on a matrix switcher. A tie line may be audio, video or both. It is essentially a logical representation of a physical cable in a system. This diagram has 12 tie lines, and those tie lines are defined in the tieLines array in configuration. +Each visible line between ports on devices represents a tie line. A tie line connects an output port on one device to an input port on another device, for example: an HDMI port on a document camera to an HDMI input on a matrix switcher. A tie line has a signal type (audio, video, audioVideo, secondaryAudio, usbInput, or usbOutput) that determines what signals can travel through it. It is essentially a logical representation of a physical cable in a system. This diagram has 12 tie lines, and those tie lines are defined in the tieLines array in configuration. ![Routing system diagram](~/docs/images/routing-system-diagram.png) Let’s go through some examples of routing, using pseudo-code: 1. Method call: “Projector 1, show Doc cam.” Routing will walk backwards through DM-RMC-3 and DM-8x8 iterating through all “wired up” ports until it finds a path back to the Doc cam. Routing will then step back through all devices in the discovered chain, switching routes on those that are switchable: Doc cam: no switching; DM 8x8: route input 3 to output 3; DM-RMC-3: no switching; Projector 1: Select input HDMI In. Route is complete. -2. Method call: “Projector 2, show Laptop, video-only.” Routing will walk backwards through DM-RMC-4, DM 8x8, DM-TX-1, iterating through all connected ports until it finds a connection to the laptop. Routing then steps back through all devices, switching video where it can: Laptop: No switching; DM-TX-1: Select HDMI in; DM 8x8: Route input 5 to output 4; DM-RMC-4: No switching; Projector 2: Select HDMI input. Route is complete. -3. Method call: “Amplifier, connect Laptop audio.” Again walking backwards to Laptop, as in #2 above. Switching will take place on DM-TX-1, DM 8x8, audio-only. +2. Method call: "Projector 2, show Laptop, video-only." Routing will walk backwards through DM-RMC-4, DM 8x8, DM-TX-1, iterating through all connected ports until it finds a connection to the laptop. During this search, only tie lines that support video signals are considered. Routing then steps back through all devices, switching video where it can: Laptop: No switching; DM-TX-1: Select HDMI in; DM 8x8: Route input 5 to output 4; DM-RMC-4: No switching; Projector 2: Select HDMI input. Route is complete. +3. Method call: "Amplifier, connect Laptop audio." Again walking backwards to Laptop, as in #2 above, but this time only tie lines supporting audio signals are evaluated. Switching will take place on DM-TX-1, DM 8x8, audio-only. 4. Very simple call: “Lobby display, show signage controller.” Routing will walk back on HDMI input 1 and immediately find the signage controller. It then does a switch to HDMI 1 on the display. All four of the above could be logically combined in a series of calls to define a possible “scene” in a room: Put Document camera on Projector 1, put Laptop on Projector 2 and the audio, put Signage on the Lobby display. They key takeaway is that the developer doesn’t need to define what is involved in making a certain route. The person configuring the system defines how it’s wired up, and the code only needs to tell a given destination to get a source, likely through configuration as well. @@ -46,6 +62,37 @@ All of the above routes can be defined in source list routing tables, covered el --- +## Routing Algorithm Details + +### Combined Signal Type Splitting + +When an `audioVideo` route is requested, the routing system automatically splits it into two independent routing operations: + +1. **Audio Route**: Finds the best path for audio signals from source to destination +2. **Video Route**: Finds the best path for video signals from source to destination + +Each route can take a different physical path through the system. For example: +- Video might travel: Laptop → DM-TX-1 → DM Matrix → Display +- Audio might travel: Laptop → DM-TX-1 → DM Matrix → Audio Processor → Amplifier + +Both routes are discovered, stored, and executed independently. This allows for flexible system designs where audio and video follow different paths. + +The same splitting behavior occurs for `Video + SecondaryAudio` requests, where video and secondary audio are routed as separate operations. + +### Signal Type Filtering + +At each step of the route discovery process, the algorithm filters tie lines based on the requested signal type: + +- **Video request**: Only considers tie lines with the `video` flag set +- **Audio request**: Only considers tie lines with the `audio` flag set +- **AudioVideo request**: Routes audio and video separately, each following their respective filtering rules + +If no tie line exists with the required signal type at any point in the chain, that path is rejected and the algorithm continues searching for an alternative route. If no valid path is found, the route request fails and no switching occurs. + +This filtering ensures that incompatible signal types never interfere with routing decisions. For example, an audio-only cable will never be selected when routing video, preventing misconfiguration errors. + +--- + ### Definitions #### Ports @@ -62,118 +109,109 @@ Source devices in Essentials must implement `IRoutingOutputs` or `IRoutingSource A midpoint is a device in the middle of the signal chain. Typically a switcher, matrix or otherwise. Examples: DM chassis; DM-TX; DM-RMC; A video codec. These devices will have input and output ports. -Passthrough midpoints (no active switching) must implement `IRoutingMidpoint`. Midpoints that actively -switch and report their current routes must implement `IRoutingMidpointWithFeedback`. +Midpoint devices must implement `IRoutingInputsOutputs`. Midpoints with switching must implement `IRouting`. #### Sink -A sink is a device at the end of a full signal path. For example, a display, amplifier, encoder, etc. Sinks typically contain only input ports. They may or may not have switching, like a display with several inputs. Classes defining sink devices must implement `IRoutingSinkWithFeedback`. +A sink is a device at the end of a full signal path. For example, a display, amplifier, encoder, etc. Sinks typically contain only input ports. They may or may not have switching, like a display with several inputs. Classes defining sink devices must implement `IRoutingSinkNoSwitching` or `IRoutingSinkWithSwitching`. #### Tie-line -A tie-line is a logical representation of a physical cable connection between two devices. It has five properties that define how the tie-line connects two devices. A configuration snippet for a single tie line connecting HDMI output 1 on a Cisco RoomKit to HDMI input 1 on a display, carrying both audio and video, is shown below. +A tie-line is a logical representation of a physical cable connection between two devices. It has five properties that define how the tie-line connects two devices. + +##### How Tie Line Types Are Determined + +The effective type of a tie line is determined by one of two methods: + +1. **Automatic (Recommended)**: When no `type` property is specified in configuration, the tie line's type is automatically calculated as the **intersection** of signal types supported by both the source and destination ports. This ensures only compatible signals are considered for routing. + + Example: If a source port supports `AudioVideo` and the destination port supports `Audio`, the tie line will have type `Audio` (the only common type). + +2. **Manual Override**: When the `type` property is explicitly set, it overrides the automatic calculation. This is useful when the physical cable supports fewer signal types than both ports are capable of. + + Example: Both ports support `AudioVideo`, but the cable only carries audio, so you set `"type": "audio"`. + +##### Validation + +At startup, tie line configurations are validated to ensure: +- Both ports exist on their respective devices +- The source and destination ports have at least one common signal type +- If a `type` override is specified, both ports must support that signal type + +Invalid tie lines will fail to build with descriptive error messages, preventing runtime routing issues. + +##### Signal Types + +Tie lines support the following signal types: + +- `audio` - Audio-only signals +- `video` - Video-only signals +- `audioVideo` - Combined audio and video (automatically split during routing) +- `secondaryAudio` - Secondary audio channel (e.g., program audio separate from microphone audio) +- `usbInput` - USB input signals +- `usbOutput` - USB output signals + +The `type` property determines which signals can travel through the tie line. During route discovery, only tie lines matching the requested signal type will be considered as valid paths. + +**Note**: In most cases, you should omit the `type` property and let the system automatically calculate it from the port capabilities. Only use it when you need to restrict the tie line to fewer signal types than the ports support or when needed for clarity. + +##### Configuration Examples + +**Example 1: Automatic type calculation (recommended)** + +Connecting an HDMI cable between devices that both support audio and video. The `type` property is omitted, so the tie line will automatically support `AudioVideo`: ```json { "sourceKey": "ciscoSparkPlusCodec-1", "sourcePort": "HdmiOut1", "destinationKey": "display-1", - "destinationPort": "HdmiIn1", - "type": "audioVideo" + "destinationPort": "HdmiIn1" } ``` -### Interfaces +**Example 2: Type override for cable limitations** -#### Routing interfaces (v3) - -The v3 routing refactor consolidated several older, overlapping interfaces into two feedback-aware -interfaces. If you're updating a device implemented against the pre-v3 API, use this table to find -its replacement: - -| Old interface (pre-v3, removed) | Current interface | -| --- | --- | -| `IRoutingInputsOutputs` | `IRoutingMidpoint` | -| `IRouting`, `IRoutingWithFeedback`, `IRoutingWithClear` | `IRoutingMidpointWithFeedback` | -| `IRoutingSink`, `IRoutingSinkWithInputPort`, `IRoutingSinkWithSwitching`, `IRoutingSinkWithSwitchingWithInputPort` | `IRoutingSinkWithFeedback` | - -**Base building-block interfaces** (unchanged): - -* `IRoutingSource` - marker interface for a device that originates a signal path; extends `IRoutingOutputs`. -* `IRoutingOutputs` - exposes `RoutingPortCollection OutputPorts`. -* `IRoutingInputs` - exposes `RoutingPortCollection InputPorts`. - -**Midpoint interfaces:** - -* `IRoutingMidpoint` - a passthrough device with both input and output ports but no active switching. Extends `IRoutingInputs` and `IRoutingOutputs`. -* `IRoutingMidpointWithFeedback` - a midpoint that actively switches and reports feedback. Extends `IRoutingMidpoint` and adds: - * `void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType)` - * `void ClearRoute(object outputSelector, eRoutingSignalType signalType)` - * `List CurrentRoutes { get; }` - * `event RouteChangedEventHandler RouteChanged` - -**Sink interface:** - -* `IRoutingSinkWithFeedback` - a routing endpoint device that can switch inputs and provides feedback. Extends `IRoutingInputs`, `IKeyName`, and `ICurrentSources` (see below), and adds: - * `void ExecuteSwitch(object inputSelector)` - * `RoutingInputPort CurrentInputPort { get; }` - * `event InputChangedEventHandler InputChanged` - -#### Current source tracking (`ICurrentSources`) - -`ICurrentSources` is a new interface (implemented by `IRoutingSinkWithFeedback`) that reports which -source is actually feeding a sink right now, per signal type: - -* `Dictionary CurrentSources { get; }` -* `Dictionary CurrentSourceKeys { get; }` -* `event EventHandler CurrentSourcesChanged` - -A new `RoutingFeedbackManager` subscribes to `RouteChanged` on every `IRoutingMidpointWithFeedback` -and `InputChanged` on every `IRoutingSinkWithFeedback` in the system, traces each change back through -the tie-line graph to the originating source (debounced ~500ms), and keeps each sink's -`ICurrentSources` state up to date automatically - developers no longer need to manually wire up -source tracking per device. - -#### Multiview layout support - -For decoders that can display several independently-routable tiles at once (e.g. a WyreStorm -NetworkHD-style multiview decoder), two additional interfaces are available: - -* `IRoutingSinkWithLayouts` - extends `IRoutingSource` and exposes `Dictionary WindowTileSinks`, one per-tile "virtual sink" that can be routed independently like any other `IRoutingSinkWithFeedback`. -* `IRoutingSinkWithLayoutState` - extends `IRoutingSinkWithLayouts` and additionally reports the current canvas/tile geometry via `MultiviewLayoutState CurrentLayout { get; }` and `event EventHandler LayoutChanged`. - -`MultiviewLayoutState` is a JSON-serializable, product-agnostic model of the canvas: `CanvasWidth`, -`CanvasHeight`, and a list of `MultiviewTileState` entries (`TileNumber`, `TileSinkKey`, `X`, `Y`, -`Width`, `Height`, `ZOrder`, `SourceDeviceKey`) - suitable for driving a routing diagram UI. - -#### Real-time routing feedback over WebSocket - -Essentials Core includes a built-in WebSocket service, `RoutingFeedbackWebsocket` -(`PepperDash.Essentials.Core.Web`), that broadcasts routing state changes to any connected client in -real time - this is what powers the live routing diagram in the -[Essentials Web Config App](https://github.com/PepperDash/essentials-web-config-app) and -[essentials-devtools](https://github.com/PepperDash/essentials-devtools). - -A client starts (or reuses) a session by sending an HTTP `GET` to the `routingFeedbackSession` API -route (e.g. `https://[processor-ip]/cws/[appId]/api/routingFeedbackSession`), which starts the -WebSocket server on a random high port (if not already running), forwards that port on the CS LAN -adapter if present, and returns a JSON body with the connection URL(s): +Both devices support `AudioVideo`, but the physical cable only carries audio. The `type` property restricts routing to audio only: ```json -{ "url": "wss://[processor-ip]:[port]/routing/join/", "fallbackUrl": "wss://..." } +{ + "sourceKey": "dmSwitcher-1", + "sourcePort": "audioVideoOut1", + "destinationKey": "amplifier-1", + "destinationPort": "audioVideoIn1", + "type": "audio" +} ``` -Once connected, the client receives a `snapshot` message describing every midpoint route, sink -input, and multiview layout currently active, followed by incremental update messages (each -debounced ~200ms) as things change: +**Example 3: Mismatched port types (automatically handled)** -| Message `type` | Sent when | Payload | -| --- | --- | --- | -| `snapshot` | On connect | `midpointRoutes`, `sinkRoutes`, `layouts` for the whole system | -| `midpointRouteChanged` | An `IRoutingMidpointWithFeedback`'s `RouteChanged` fires | `deviceKey`, `routes: [{ inputPortKey, outputPortKey, signalType }]` | -| `sinkInputChanged` | An `IRoutingSinkWithFeedback`'s `InputChanged` fires | `deviceKey`, `inputPortKey`, `sourceDeviceKey`, `signalType` | -| `layoutChanged` | An `IRoutingSinkWithLayoutState`'s `LayoutChanged` fires | `deviceKey`, `layout` (a `MultiviewLayoutState`) | +Source only supports audio, destination supports both. No `type` needed—the tie line will automatically be `Audio`: -Per-tile sinks belonging to an `IRoutingSinkWithLayouts` device are reported to clients as qualified -input ports on their parent device's node, rather than as separate graph nodes, so a routing diagram -can render a multiview decoder as a single device with per-tile inputs. +```json +{ + "sourceKey": "audioProcessor-1", + "sourcePort": "audioOut1", + "destinationKey": "dmSwitcher-1", + "destinationPort": "audioVideoIn1" +} +``` + +**Invalid Example: Incompatible types** + +This configuration will **fail validation** at startup because the ports have no common signal types: + +```json +{ + "sourceKey": "audioProcessor-1", + "sourcePort": "audioOut1", + "destinationKey": "display-1", + "destinationPort": "hdmiIn1", + "type": "video" +} +``` +Error: `"Override type 'Video' is not supported by source port 'audioOut1' (type: Audio)"` + +### Interfaces + +Todo: Define Interfaces IRouting, IRoutingOutputs, IRoutingInputs diff --git a/docs/docs/technical-docs/Mobile-Control.md b/docs/docs/technical-docs/Mobile-Control.md deleted file mode 100644 index 0215f897..00000000 --- a/docs/docs/technical-docs/Mobile-Control.md +++ /dev/null @@ -1,240 +0,0 @@ -# Mobile Control: architecture and client communication - -## TL;DR - -Mobile Control is a WebSocket-based messaging layer built into Essentials. Devices expose small -"messenger" classes that register JSON message paths (e.g. `/device/{key}/startMeeting`) with a -central `MobileControlSystemController`. Clients (typically a React app built on -[`@pepperdash/mobile-control-react-app-core`](https://github.com/PepperDash/mobile-control-react-app-core)) -connect over WebSocket with a per-room token, send the same JSON envelope to invoke device methods, -and receive unsolicited state-update messages that a Redux store uses to keep device/room state in -sync in real time. - -## Server-side architecture - -### Projects involved - -* `PepperDash.Essentials.MobileControl` - the `MobileControlSystemController` device, its - WebSocket server (`WebSocketServer/MobileControlWebsocketServer.cs`), room bridges, and - touchpanel controller. -* `PepperDash.Essentials.MobileControl.Messengers` (namespace `PepperDash.Essentials.AppServer.Messengers`) - - the `MessengerBase` class and the library of built-in messengers for common device interfaces - (`IHasStartMeetingMessenger`, `IHasMeetingInfoMessenger`, `ICommunicationMonitorMessenger`, - `IRoutingMidpointWithFeedbackMessenger`, etc.), plus the message envelope types. -* `PepperDash.Essentials.Core.DeviceTypeInterfaces` - the public contracts: - `IMobileControl`, `IMobileControlMessenger`, `IMobileControlMessengerWithSubscriptions`, - `IMobileControlRoomMessenger`, `IMobileControlAction`, `IMobileControlMessage`, and the - touchpanel-controller interfaces. - -### Direct Server vs. Edge Server - -Mobile Control supports two (non-exclusive) transport modes, configured on the `MobileControlConfig` -device: - -* **Direct Server** - `MobileControlWebsocketServer` runs an HTTPS WebSocket server directly on the - processor hardware. Clients on the same network connect straight to the processor - lowest - latency, no external dependency. This is what `mobile-control-react-app-core`'s local dev flow - and the deployed `mcUserApp` React app use. -* **Edge Server** (API server / cloud gateway) - the processor instead makes an *outbound* WebSocket - connection to an external Mobile Control server, which relays messages between it and remote - clients. Useful when clients can't reach the processor directly (e.g. no local network access). - -Both modes can be enabled simultaneously; outgoing messages are queued and sent down whichever -transport(s) are active. - -### The WebSocket endpoint (Direct Server) - -`MobileControlWebsocketServer` (`WebSocketServer/MobileControlWebsocketServer.cs`) hosts the -Direct Server: - -* **Path**: `/mc/api/ui/join/` (`_wsPath`) -* **Port**: `50000 + ` by default (e.g. program slot 2 → port `50002`), or a - custom port from config -* **User app**: static files are served from `/user/programX/mcUserApp` (`_appPath`) at the base - href `/mc/app` (`_userAppBaseHref`) - this is exactly the `mcUserApp` deployment directory used - when [deploying](../Get-started.md) a built React app. - -### Connecting and authentication (tokens) - -A client doesn't connect straight to the WebSocket - it first needs a **token** identifying which -room/UI-client slot it's joining: - -1. **Get a token** - on the processor console, run: - ``` - mobileinfo:[programSlot] - ``` - This prints the Direct Server port, any existing UI-client tokens, and full connect URLs (e.g. - `http://[ip]:[port]/mc/app?token=[token]`). New tokens can also be added dynamically - (`mobileadduiclient:[programSlot] [roomKey]`) - see the console command's help text for details. -2. **Join a room** - the client calls `GET {apiPath}/ui/joinroom?token={token}` over HTTPS, which - validates the token and returns room data including a generated `clientId`. -3. **Open the WebSocket** - the client connects to - `wss://[processor-ip]:[port]/mc/api/ui/join/{token}?clientId={clientId}`. - -Each connected client is tracked as a `UiClient`; disconnecting removes its subscriptions from -every messenger it was subscribed to. - -### The message envelope - -Every message sent in either direction is the same simple JSON envelope -(`MobileControlMessage`, namespace `PepperDash.Essentials.AppServer.Messengers`): - -```json -{ - "type": "/device/zoomRoom1/startMeeting", - "clientId": "abc-123", - "content": { "value": 30 } -} -``` - -* `type` - a path identifying the target device/room and action (client → server), or the - device/room whose state is being reported (server → client). -* `clientId` - which client sent the message, or (server → client) which single client a targeted - reply is meant for; omitted/ignored for broadcasts to all subscribers. -* `content` - a `JToken` payload. For simple values, `MobileControlSimpleContent` wraps a single - `value` property (as in the example above); for state updates, `content` is the device's full - serialized state object. - -### The messenger pattern - -Each device that wants to be controllable/observable over Mobile Control gets a small **messenger** -class deriving from `MessengerBase` (`PepperDash.Essentials.AppServer.Messengers`): - -```csharp -public class IHasStartMeetingMessenger : MessengerBase -{ - // ... - protected override void RegisterActions() - { - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/startMeeting", (id, content) => - { - var msg = content.ToObject>(); - _startMeeting.StartMeeting(msg?.Value ?? _startMeeting.DefaultMeetingDurationMin); - }); - - AddAction("/leaveMeeting", (id, content) => _startMeeting.LeaveMeeting()); - } -} -``` - -* The messenger is constructed with a **base `MessagePath`** (e.g. `/device/zoomRoom1`) and calls - `AddAction(subPath, handler)` for each supported action, relative to that base path. -* `RegisterWithAppServer(IMobileControl appServerController)` registers the messenger's - `HandleMessage` callback with the system controller via `IMobileControl.AddAction(...)`. -* When an incoming message's `type` starts with the messenger's `MessagePath`, `HandleMessage` - strips that prefix (leaving e.g. `/startMeeting`) and dispatches to the matching registered - action. The sending client is automatically added to that messenger's subscriber list, so it - receives future unsolicited feedback from it. -* To push state out, messengers call `PostStatusMessage(DeviceStateMessageBase message, clientId)` - - omitting `clientId` broadcasts to every subscribed client; passing one targets a single client - (e.g. replying to a `/fullStatus` request). - -### How devices opt in - -`EssentialsDevice.CustomActivate()` calls a virtual `CreateMobileControlMessengers()` hook once all -devices have activated. A device (or a room) overrides this method to look up the `IMobileControl` -device and construct/register whichever messengers it needs: - -```csharp -protected override void CreateMobileControlMessengers() -{ - var mc = DeviceManager.AllDevices.OfType().FirstOrDefault(); - if (mc == null) return; - - var messenger = new IHasStartMeetingMessenger("zoomRoom1-startMeeting", "/device/zoomRoom1", this); - messenger.RegisterWithAppServer(mc); -} -``` - -Rooms typically implement `IMobileControlRoomMessenger` and register a room-level messenger at a -`/room/{roomKey}` path (via a `MobileControlBridgeBase`/`MobileControlEssentialsRoomBridge`), in -addition to any per-device messengers. - -## Client-side architecture (React) - -`@pepperdash/mobile-control-react-app-core` provides the client building blocks used by apps like -[beincourt-pv2-react-app](https://github.com/pepperdash-beincourt/beincourt-pv2-react-app): - -### Connection lifecycle - -The real work happens in a Redux middleware (`src/lib/store/middleware/websocketMiddleware.ts`); -`WebsocketProvider`/`WebsocketContext` are a thin, backward-compatible React Context wrapper around -it that simply dispatches Redux actions (`wsConnect`, `wsSendMessage`, `wsReconnect`, ...). - -1. On mount, `wsConnect()` is dispatched. -2. The middleware reads `apiPath` from the app's local config (`_config.local.json`/ - `_config.default.json`) and the connection `token` (from the URL's `?token=` query param). -3. It calls `GET {apiPath}/ui/joinroom?token={token}` to validate the token and get room data - (including a `clientId`). -4. It opens a WebSocket at `{apiPath with ws(s) scheme}/ui/join/{token}?clientId={clientId}`. -5. On specific close codes (e.g. `4000` user code changed, `4002` room combination changed) it stops - auto-reconnecting and surfaces an error; otherwise it automatically retries. - -### Sending messages - -```ts -const { sendMessage } = useWebsocketContext(); -sendMessage('/device/zoomRoom1/startMeeting', { value: 30 }); -``` - -`sendMessage` serializes `{ type, clientId, content }` (the same envelope described above) and -sends it over the open WebSocket. - -### Receiving messages / state hydration - -Incoming messages are routed by their `type` prefix: - -| Prefix | Handling | -| --- | --- | -| `/system/*` | Internal system messages (user code, touchpanel key, room-combination/device-interface changes, initial sync complete, ...) | -| `/event/*` | Dispatched to any handlers registered via `addEventHandler(eventType, key, callback)` | -| `/room/*` | `dispatch(roomsActions.setRoomState(message))` | -| `/device/*` | `dispatch(devicesActions.setDeviceState(message))` | - -Device/room state lives in Redux slices keyed by device/room key, so any component can read the -latest known state for a given key. - -### The hook pattern - -Each supported device interface has a small hook that combines reading state with calling actions, -e.g. `useIHasStartMeeting`: - -```ts -export function useIHasStartMeeting(key: string): IHasStartMeetingReturn | undefined { - const { sendMessage } = useWebsocketContext(); - const state = useGetDevice(key); // reads devices[key] from Redux - - return useMemo(() => { - if (!state) return undefined; - const path = `/device/${key}`; - return { - state, - startMeeting: (durationMin?: number) => sendMessage(`${path}/startMeeting`, { value: durationMin }), - leaveMeeting: () => sendMessage(`${path}/leaveMeeting`, null), - }; - }, [key, sendMessage, state]); -} -``` - -Components use these hooks to read live device state and call device actions without needing to -know anything about the WebSocket transport underneath. - -## Full round-trip example: starting a meeting - -1. **UI**: user clicks "Start Meeting"; component calls `startMeeting(30)` from `useIHasStartMeeting('zoomRoom1')`. -2. **Client → server**: `sendMessage('/device/zoomRoom1/startMeeting', { value: 30 })` sends - `{"type":"/device/zoomRoom1/startMeeting","clientId":"abc-123","content":{"value":30}}` over the WebSocket. -3. **Server routing**: the system controller matches `/device/zoomRoom1` against the registered - `IHasStartMeetingMessenger`'s `MessagePath`, strips the prefix to get `/startMeeting`, and invokes - its registered action, which calls `StartMeeting(30)` on the underlying device. -4. **Device fires feedback**: the device's meeting-started feedback fires. -5. **Server → clients**: the messenger calls `PostStatusMessage(...)`, broadcasting - `{"type":"/device/zoomRoom1","clientId":null,"content":{ ...state }}` to every client subscribed - to that messenger. -6. **Client receives**: the message's `type` starts with `/device/`, so - `dispatch(devicesActions.setDeviceState(message))` merges the new state into Redux under - `devices['zoomRoom1']`. -7. **UI updates**: `useIHasStartMeeting('zoomRoom1')`'s `state` reflects the new meeting info on the - next render. diff --git a/docs/docs/toc.yml b/docs/docs/toc.yml index 9ec7ee87..820de992 100644 --- a/docs/docs/toc.yml +++ b/docs/docs/toc.yml @@ -38,8 +38,6 @@ href: technical-docs/Plugins.md - name: Communication Basics href: technical-docs/Communication-Basics.md - - name: Mobile Control - href: technical-docs/Mobile-Control.md - name: Debugging href: technical-docs/Debugging.md - name: Feedback Classes diff --git a/docs/docs/usage/GenericComm.md b/docs/docs/usage/GenericComm.md index 243536e5..16f467e2 100644 --- a/docs/docs/usage/GenericComm.md +++ b/docs/docs/usage/GenericComm.md @@ -183,11 +183,12 @@ namespace PepperDash.Core Cresnet = 8, Cec = 9, Udp = 10, + UdpClient = 11, } } ``` -These enumerations are not case sensitive. Not all methods are valid for a ```genericComm``` device. For a comport, the only valid type would be ```Com```. For a direct network socket, valid options are ```Ssh```, ```Tcpip```, ```Telnet```, and ```Udp```. +These enumerations are not case sensitive. Not all methods are valid for a ```genericComm``` device. For a comport, the only valid type would be ```Com```. For a direct network socket, valid options are ```Ssh```, ```Tcpip```, ```Telnet```, ```UdpClient```, and ```Udp```. ##### ComParams @@ -287,7 +288,7 @@ This property maps to the number of the port on the device you have mapped the r ##### TcpSshParams -A ```Ssh```, ```TcpIp```, or ```Udp``` device requires a ```tcpSshProperties``` object to set the propeties of the socket. +A ```Ssh```, ```TcpIp```, ```UdpClient```, or ```Udp``` device requires a ```tcpSshProperties``` object to set the propeties of the socket. ```Json { @@ -304,7 +305,7 @@ A ```Ssh```, ```TcpIp```, or ```Udp``` device requires a ```tcpSshProperties``` **```address```** -This is the IP address, hostname, or FQDN of the resource you wish to open a socket to. In the case of a UDP device, you can set either a single whitelist address with this data, or an appropriate broadcast address. +This is the IP address, hostname, or FQDN of the resource you wish to open a socket to. Use ```UdpClient``` for outbound UDP to a remote endpoint. Use ```Udp``` when you need Essentials to bind a local UDP listener. **```port```** diff --git a/runtimeconfig.json b/runtimeconfig.json deleted file mode 100644 index 32835bac..00000000 --- a/runtimeconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "runtimeOptions": { - "configProperties": { - "System.Globalization.Invariant": false - } - } -} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bdc59cb9..235fee48 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 3.0.0-local + 2.42.1-local $(Version) PepperDash Technology PepperDash Technology diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 50816cf4..ce6c0eed 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,14 +1,14 @@ - + true build; - + true build; - + true build; diff --git a/src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs b/src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs deleted file mode 100644 index c5e18c5a..00000000 --- a/src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Allows pre-registration of Crestron service implementations before the Debug -/// static class initialises. Call from the composition root -/// (e.g. ControlSystem constructor) before any code touches Debug.*. -/// Test projects should call it with no-op / in-memory implementations so that the -/// Debug static constructor never tries to reach the real Crestron SDK. -/// -public static class DebugServiceRegistration -{ - /// Gets the registered environment abstraction, or null if not registered. - public static ICrestronEnvironment? Environment { get; private set; } - - /// Gets the registered console abstraction, or null if not registered. - public static ICrestronConsole? Console { get; private set; } - - /// Gets the registered data-store abstraction, or null if not registered. - public static ICrestronDataStore? DataStore { get; private set; } - - /// - /// Registers the service implementations that Debug will use when its - /// static constructor runs. Any parameter may be null to leave the - /// corresponding service unregistered (the Debug class will skip that - /// capability gracefully). - /// - public static void Register( - ICrestronEnvironment? environment, - ICrestronConsole? console, - ICrestronDataStore? dataStore) - { - Environment = environment; - Console = console; - DataStore = dataStore; - } -} diff --git a/src/PepperDash.Core.Abstractions/Enums.cs b/src/PepperDash.Core.Abstractions/Enums.cs deleted file mode 100644 index cd6052e6..00000000 --- a/src/PepperDash.Core.Abstractions/Enums.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Mirrors Crestron's eDevicePlatform without requiring the Crestron SDK. -/// -public enum DevicePlatform -{ - /// Hardware appliance (e.g. CP4, MC4). - Appliance, - /// Crestron Virtual Control / server runtime. - Server, -} - -/// -/// Mirrors Crestron's eRuntimeEnvironment. -/// -public enum RuntimeEnvironment -{ - /// SimplSharpPro program slot (hardware 4-series). - SimplSharpPro, - /// SimplSharp (older 3-series or server environments). - SimplSharp, - /// Any other environment — check for completeness. - Other, -} - -/// -/// Mirrors Crestron's ConsoleAccessLevelEnum. -/// -public enum ConsoleAccessLevel -{ - AccessAdministrator = 0, - AccessOperator = 1, - AccessProgrammer = 2, -} - -/// -/// Mirrors Crestron's eProgramStatusEventType. -/// -public enum ProgramStatusEventType -{ - Starting, - Stopping, - Paused, - Resumed, -} - -/// -/// Mirrors the event type used by Crestron's EthernetEventArgs. -/// -public enum EthernetEventType -{ - LinkDown = 0, - LinkUp = 1, -} - -/// -/// Event args for Crestron ethernet link events. -/// -public class PepperDashEthernetEventArgs : EventArgs -{ - public EthernetEventType EthernetEventType { get; } - public short EthernetAdapter { get; } - - public PepperDashEthernetEventArgs(EthernetEventType eventType, short adapter) - { - EthernetEventType = eventType; - EthernetAdapter = adapter; - } -} - -/// -/// Mirrors the set of CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET values -/// used across this codebase — does not aim to be exhaustive. -/// -public enum EthernetParameterType -{ - GetCurrentIpAddress, - GetHostname, - GetDomainName, - GetLinkStatus, - GetCurrentDhcpState, - GetCurrentIpMask, - GetCurrentRouter, - GetMacAddress, - GetDnsServer, -} - -/// -/// Mirrors Crestron's SocketStatus without requiring the Crestron SDK. -/// -public enum PepperDashSocketStatus -{ - SocketNotConnected = 0, - SocketConnected = 2, - SocketConnectionInProgress = 6, - SocketConnectFailed = 11, - SocketDisconnecting = 12, - SocketBrokenRemotely = 7, -} diff --git a/src/PepperDash.Core.Abstractions/ICrestronConsole.cs b/src/PepperDash.Core.Abstractions/ICrestronConsole.cs deleted file mode 100644 index 335c4ee0..00000000 --- a/src/PepperDash.Core.Abstractions/ICrestronConsole.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Abstracts Crestron.SimplSharp.CrestronConsole to allow unit testing -/// without the Crestron SDK. -/// -public interface ICrestronConsole -{ - /// Prints a line to the Crestron console/telnet output. - void PrintLine(string message); - - /// Prints text (without newline) to the Crestron console/telnet output. - void Print(string message); - - /// - /// Sends a response string to the console for the currently-executing console command. - /// - void ConsoleCommandResponse(string message); - - /// - /// Registers a new command with the Crestron console. - /// - /// Handler invoked when the command is typed. - /// Command name (no spaces). - /// Help text shown by the Crestron console. - /// Minimum access level required to run the command. - void AddNewConsoleCommand( - Action callback, - string command, - string helpText, - ConsoleAccessLevel accessLevel); -} diff --git a/src/PepperDash.Core.Abstractions/ICrestronDataStore.cs b/src/PepperDash.Core.Abstractions/ICrestronDataStore.cs deleted file mode 100644 index 96fd1856..00000000 --- a/src/PepperDash.Core.Abstractions/ICrestronDataStore.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Abstracts Crestron.SimplSharp.CrestronDataStore.CrestronDataStoreStatic -/// to allow unit testing without the Crestron SDK. -/// -public interface ICrestronDataStore -{ - /// Initialises the data store. Must be called once before any other operation. - void InitStore(); - - /// Reads an integer value from the local (program-slot) store. - /// true if the value was found and read successfully. - bool TryGetLocalInt(string key, out int value); - - /// Writes an integer value to the local (program-slot) store. - /// true on success. - bool SetLocalInt(string key, int value); - - /// Writes an unsigned integer value to the local (program-slot) store. - /// true on success. - bool SetLocalUint(string key, uint value); - - /// Reads a boolean value from the local (program-slot) store. - /// true if the value was found and read successfully. - bool TryGetLocalBool(string key, out bool value); - - /// Writes a boolean value to the local (program-slot) store. - /// true on success. - bool SetLocalBool(string key, bool value); -} diff --git a/src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs b/src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs deleted file mode 100644 index 1e73c83f..00000000 --- a/src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Abstracts Crestron.SimplSharp.CrestronEnvironment to allow unit testing -/// without the Crestron SDK. -/// -public interface ICrestronEnvironment -{ - /// Gets the platform the program is executing on. - DevicePlatform DevicePlatform { get; } - - /// Gets the current runtime environment. - RuntimeEnvironment RuntimeEnvironment { get; } - - /// Gets the platform-appropriate newline string. - string NewLine { get; } - - /// Gets the application number (program slot). - uint ApplicationNumber { get; } - - /// Gets the room ID (used in Crestron Virtual Control / server environments). - uint RoomId { get; } - - /// Raised when program status changes (starting, stopping, etc.). - event EventHandler ProgramStatusChanged; - - /// Raised when the ethernet link changes state. - event EventHandler EthernetEventReceived; - - /// Gets the application root directory path. - string GetApplicationRootDirectory(); - - /// - /// Returns true when running on real Crestron hardware. - /// Returns false in test / dev environments so that SDK-specific - /// sinks and enrichers can be safely skipped. - /// - bool IsHardwareRuntime { get; } -} - -/// -/// Event args for . -/// -public class ProgramStatusEventArgs : EventArgs -{ - public ProgramStatusEventType EventType { get; } - - public ProgramStatusEventArgs(ProgramStatusEventType eventType) - { - EventType = eventType; - } -} diff --git a/src/PepperDash.Core.Abstractions/IEthernetHelper.cs b/src/PepperDash.Core.Abstractions/IEthernetHelper.cs deleted file mode 100644 index 0ae1c83b..00000000 --- a/src/PepperDash.Core.Abstractions/IEthernetHelper.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace PepperDash.Core.Abstractions; - -/// -/// Abstracts Crestron.SimplSharp.CrestronEthernetHelper to allow unit testing -/// without the Crestron SDK. -/// -public interface IEthernetHelper -{ - /// - /// Returns a network parameter string for the specified adapter. - /// - /// The parameter to retrieve. - /// Ethernet adapter index (0 = LAN A). - /// String value of the requested parameter. - string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId); -} diff --git a/src/PepperDash.Core.Abstractions/PepperDash.Core.Abstractions.csproj b/src/PepperDash.Core.Abstractions/PepperDash.Core.Abstractions.csproj deleted file mode 100644 index c1cdf5bc..00000000 --- a/src/PepperDash.Core.Abstractions/PepperDash.Core.Abstractions.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - PepperDash.Core.Abstractions - PepperDash.Core.Abstractions - net8.0 - enable - true - en - PepperDash Core Abstractions - PepperDash Technologies - git - https://github.com/PepperDash/PepperDashCore - enable - false - $(Version) - true - - - diff --git a/src/PepperDash.Core.Tests/Devices/DeviceTests.cs b/src/PepperDash.Core.Tests/Devices/DeviceTests.cs deleted file mode 100644 index 2450e401..00000000 --- a/src/PepperDash.Core.Tests/Devices/DeviceTests.cs +++ /dev/null @@ -1,323 +0,0 @@ -using FluentAssertions; -using PepperDash.Core; -using Xunit; - -namespace PepperDash.Core.Tests.Devices; - -/// -/// Tests for — the base class for all PepperDash devices. -/// These run without Crestron hardware; Debug is initialized with fakes via TestInitializer. -/// -public class DeviceTests -{ - // ----------------------------------------------------------------------- - // Construction - // ----------------------------------------------------------------------- - - [Fact] - public void Constructor_SingleArg_SetsKey() - { - var device = new Device("my-device"); - - device.Key.Should().Be("my-device"); - } - - [Fact] - public void Constructor_SingleArg_SetsNameToEmpty() - { - var device = new Device("my-device"); - - device.Name.Should().BeEmpty(); - } - - [Fact] - public void Constructor_TwoArg_SetsKeyAndName() - { - var device = new Device("my-device", "My Device"); - - device.Key.Should().Be("my-device"); - device.Name.Should().Be("My Device"); - } - - [Fact] - public void Constructor_KeyWithDot_StillSetsKey() - { - // The dot triggers a debug log warning but must not prevent construction. - var device = new Device("parent.child"); - - device.Key.Should().Be("parent.child"); - } - - // ----------------------------------------------------------------------- - // ToString - // ----------------------------------------------------------------------- - - [Fact] - public void ToString_WithName_FormatsKeyDashName() - { - var device = new Device("cam-01", "Front Camera"); - - device.ToString().Should().Be("cam-01 - Front Camera"); - } - - [Fact] - public void ToString_WithoutName_UsesDashPlaceholder() - { - var device = new Device("cam-01"); - - device.ToString().Should().Be("cam-01 - ---"); - } - - // ----------------------------------------------------------------------- - // DefaultDevice - // ----------------------------------------------------------------------- - - [Fact] - public void DefaultDevice_IsNotNull() - { - Device.DefaultDevice.Should().NotBeNull(); - } - - [Fact] - public void DefaultDevice_HasKeyDefault() - { - Device.DefaultDevice.Key.Should().Be("Default"); - } - - // ----------------------------------------------------------------------- - // CustomActivate / Activate / Deactivate / Initialize - // ----------------------------------------------------------------------- - - [Fact] - public void CustomActivate_DefaultReturnTrue() - { - var device = new TestDevice("d1"); - - device.CallCustomActivate().Should().BeTrue(); - } - - [Fact] - public void Deactivate_DefaultReturnsTrue() - { - var device = new Device("d1"); - - device.Deactivate().Should().BeTrue(); - } - - [Fact] - public void Activate_CallsCustomActivate_AndReturnsItsResult() - { - var stub = new ActivateTrackingDevice("d1", result: false); - - stub.Activate().Should().BeFalse(); - stub.CustomActivateCalled.Should().BeTrue(); - } - - [Fact] - public void Activate_TrueWhenCustomActivateReturnsTrue() - { - var stub = new ActivateTrackingDevice("d1", result: true); - - stub.Activate().Should().BeTrue(); - } - - [Fact] - public void Initialize_DoesNotThrow() - { - var device = new TestDevice("d1"); - var act = () => device.CallInitialize(); - - act.Should().NotThrow(); - } - - // ----------------------------------------------------------------------- - // PreActivate - // ----------------------------------------------------------------------- - - [Fact] - public void PreActivate_NoActions_DoesNotThrow() - { - var device = new TestDevice("d1"); - var act = () => device.PreActivate(); - - act.Should().NotThrow(); - } - - [Fact] - public void PreActivate_RunsRegisteredActionsInOrder() - { - var device = new TestDevice("d1"); - var order = new List(); - - device.AddPreActivationAction(() => order.Add(1)); - device.AddPreActivationAction(() => order.Add(2)); - device.AddPreActivationAction(() => order.Add(3)); - - device.PreActivate(); - - order.Should().Equal(1, 2, 3); - } - - [Fact] - public void PreActivate_ContinuesAfterFaultingAction() - { - var device = new TestDevice("d1"); - var reached = false; - - device.AddPreActivationAction(() => throw new InvalidOperationException("boom")); - device.AddPreActivationAction(() => reached = true); - - var act = () => device.PreActivate(); - - act.Should().NotThrow("exceptions in individual actions must be caught internally"); - reached.Should().BeTrue("actions after a faulting action must still run"); - } - - // ----------------------------------------------------------------------- - // PostActivate - // ----------------------------------------------------------------------- - - [Fact] - public void PostActivate_NoActions_DoesNotThrow() - { - var device = new TestDevice("d1"); - var act = () => device.PostActivate(); - - act.Should().NotThrow(); - } - - [Fact] - public void PostActivate_RunsRegisteredActionsInOrder() - { - var device = new TestDevice("d1"); - var order = new List(); - - device.AddPostActivationAction(() => order.Add(1)); - device.AddPostActivationAction(() => order.Add(2)); - - device.PostActivate(); - - order.Should().Equal(1, 2); - } - - [Fact] - public void PostActivate_ContinuesAfterFaultingAction() - { - var device = new TestDevice("d1"); - var reached = false; - - device.AddPostActivationAction(() => throw new Exception("boom")); - device.AddPostActivationAction(() => reached = true); - - var act = () => device.PostActivate(); - - act.Should().NotThrow(); - reached.Should().BeTrue(); - } - - // ----------------------------------------------------------------------- - // Pre and Post actions are independent lists - // ----------------------------------------------------------------------- - - [Fact] - public void PreActivationActions_DoNotRunOnPostActivate() - { - var device = new TestDevice("d1"); - var preRan = false; - - device.AddPreActivationAction(() => preRan = true); - device.PostActivate(); - - preRan.Should().BeFalse(); - } - - [Fact] - public void PostActivationActions_DoNotRunOnPreActivate() - { - var device = new TestDevice("d1"); - var postRan = false; - - device.AddPostActivationAction(() => postRan = true); - device.PreActivate(); - - postRan.Should().BeFalse(); - } - - // ----------------------------------------------------------------------- - // OnFalse - // ----------------------------------------------------------------------- - - [Fact] - public void OnFalse_FiresAction_WhenBoolIsFalse() - { - var device = new Device("d1"); - var fired = false; - - device.OnFalse(false, () => fired = true); - - fired.Should().BeTrue(); - } - - [Fact] - public void OnFalse_DoesNotFireAction_WhenBoolIsTrue() - { - var device = new Device("d1"); - var fired = false; - - device.OnFalse(true, () => fired = true); - - fired.Should().BeFalse(); - } - - [Fact] - public void OnFalse_DoesNotFireAction_ForNonBoolType() - { - var device = new Device("d1"); - var fired = false; - - device.OnFalse("not a bool", () => fired = true); - device.OnFalse(0, () => fired = true); - device.OnFalse(null!, () => fired = true); - - fired.Should().BeFalse(); - } - - // ----------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------- - - /// - /// Exposes protected Device members so test methods can call them directly. - /// - private class TestDevice : Device - { - public TestDevice(string key) : base(key) { } - public TestDevice(string key, string name) : base(key, name) { } - - public void AddPreActivationAction(Action act) => base.AddPreActivationAction(act); - public void AddPostActivationAction(Action act) => base.AddPostActivationAction(act); - public bool CallCustomActivate() => base.CustomActivate(); - public void CallInitialize() => base.Initialize(); - } - - /// - /// Records whether CustomActivate was invoked and returns a configured result. - /// Used to verify Activate() correctly delegates to CustomActivate(). - /// - private sealed class ActivateTrackingDevice : Device - { - private readonly bool _result; - public bool CustomActivateCalled { get; private set; } - - public ActivateTrackingDevice(string key, bool result = true) : base(key) - { - _result = result; - } - - protected override bool CustomActivate() - { - CustomActivateCalled = true; - return _result; - } - } -} diff --git a/src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs b/src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs deleted file mode 100644 index afd5c324..00000000 --- a/src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs +++ /dev/null @@ -1,38 +0,0 @@ -using PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Tests.Fakes; - -/// -/// No-op ICrestronConsole that captures output for test assertions. -/// -public class CapturingCrestronConsole : ICrestronConsole -{ - public List Lines { get; } = new(); - public List CommandResponses { get; } = new(); - public List<(string Command, string HelpText)> RegisteredCommands { get; } = new(); - - public void PrintLine(string message) => Lines.Add(message); - public void Print(string message) => Lines.Add(message); - public void ConsoleCommandResponse(string message) => CommandResponses.Add(message); - - public void AddNewConsoleCommand( - Action callback, - string command, - string helpText, - ConsoleAccessLevel accessLevel) - { - RegisteredCommands.Add((command, helpText)); - } -} - -/// -/// Minimal no-op ICrestronConsole that discards all output. Useful when you only -/// care about the system under test and not what it logs. -/// -public class NoOpCrestronConsole : ICrestronConsole -{ - public void PrintLine(string message) { } - public void Print(string message) { } - public void ConsoleCommandResponse(string message) { } - public void AddNewConsoleCommand(Action _, string __, string ___, ConsoleAccessLevel ____) { } -} diff --git a/src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs b/src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs deleted file mode 100644 index 1792206d..00000000 --- a/src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs +++ /dev/null @@ -1,49 +0,0 @@ -using PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Tests.Fakes; - -/// -/// Configurable ICrestronEnvironment for unit tests. -/// Defaults: Appliance / SimplSharpPro / ApplicationNumber=1. -/// -public class FakeCrestronEnvironment : ICrestronEnvironment -{ - public DevicePlatform DevicePlatform { get; set; } = DevicePlatform.Appliance; - public RuntimeEnvironment RuntimeEnvironment { get; set; } = RuntimeEnvironment.SimplSharpPro; - public string NewLine { get; set; } = "\r\n"; - public uint ApplicationNumber { get; set; } = 1; - public uint RoomId { get; set; } = 0; - - public event EventHandler? ProgramStatusChanged; - public event EventHandler? EthernetEventReceived; - - public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath(); - - /// - public bool IsHardwareRuntime => false; - - /// Simulates a program status event for tests. - public void RaiseProgramStatus(ProgramStatusEventType type) => - ProgramStatusChanged?.Invoke(this, new ProgramStatusEventArgs(type)); - - /// Simulates an ethernet event for tests. - public void RaiseEthernetEvent(EthernetEventType type, short adapter = 0) => - EthernetEventReceived?.Invoke(this, new PepperDashEthernetEventArgs(type, adapter)); -} - -/// -/// No-op IEthernetHelper that returns configurable values. -/// -public class FakeEthernetHelper : IEthernetHelper -{ - private readonly Dictionary _values = new(); - - public FakeEthernetHelper Seed(EthernetParameterType param, string value) - { - _values[param] = value; - return this; - } - - public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId) => - _values.TryGetValue(parameter, out var v) ? v : string.Empty; -} diff --git a/src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs b/src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs deleted file mode 100644 index 7cc2b618..00000000 --- a/src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs +++ /dev/null @@ -1,59 +0,0 @@ -using PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Tests.Fakes; - -/// -/// In-memory ICrestronDataStore backed by a dictionary. -/// Use in unit tests to verify that keys are read from and written to the store correctly. -/// -public class InMemoryCrestronDataStore : ICrestronDataStore -{ - private readonly Dictionary _store = new(); - - public bool Initialized { get; private set; } - - public void InitStore() => Initialized = true; - - public bool TryGetLocalInt(string key, out int value) - { - if (_store.TryGetValue(key, out var raw) && raw is int i) - { - value = i; - return true; - } - value = 0; - return false; - } - - public bool SetLocalInt(string key, int value) - { - _store[key] = value; - return true; - } - - public bool SetLocalUint(string key, uint value) - { - _store[key] = (int)value; - return true; - } - - public bool TryGetLocalBool(string key, out bool value) - { - if (_store.TryGetValue(key, out var raw) && raw is bool b) - { - value = b; - return true; - } - value = false; - return false; - } - - public bool SetLocalBool(string key, bool value) - { - _store[key] = value; - return true; - } - - /// Seeds a key for testing read paths. - public void Seed(string key, object value) => _store[key] = value; -} diff --git a/src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs b/src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs deleted file mode 100644 index 13ae0f9e..00000000 --- a/src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs +++ /dev/null @@ -1,171 +0,0 @@ -using FluentAssertions; -using PepperDash.Core.Abstractions; -using PepperDash.Core.Tests.Fakes; -using Xunit; - -namespace PepperDash.Core.Tests.Logging; - -/// -/// Tests for Debug-related service interfaces and implementations. -/// These tests verify the behaviour of the abstractions in isolation (no Crestron SDK required). -/// -public class DebugServiceTests -{ - // ----------------------------------------------------------------------- - // ICrestronDataStore — InMemoryCrestronDataStore - // ----------------------------------------------------------------------- - - [Fact] - public void DataStore_InitStore_SetsInitializedFlag() - { - var store = new InMemoryCrestronDataStore(); - store.Initialized.Should().BeFalse("not yet initialized"); - - store.InitStore(); - - store.Initialized.Should().BeTrue(); - } - - [Fact] - public void DataStore_SetAndGetLocalInt_RoundTrips() - { - var store = new InMemoryCrestronDataStore(); - - store.SetLocalInt("MyKey", 42).Should().BeTrue(); - store.TryGetLocalInt("MyKey", out var value).Should().BeTrue(); - value.Should().Be(42); - } - - [Fact] - public void DataStore_TryGetLocalInt_ReturnsFalse_WhenKeyAbsent() - { - var store = new InMemoryCrestronDataStore(); - - store.TryGetLocalInt("Missing", out var value).Should().BeFalse(); - value.Should().Be(0); - } - - [Fact] - public void DataStore_SetAndGetLocalBool_RoundTrips() - { - var store = new InMemoryCrestronDataStore(); - - store.SetLocalBool("FlagKey", true).Should().BeTrue(); - store.TryGetLocalBool("FlagKey", out var value).Should().BeTrue(); - value.Should().BeTrue(); - } - - [Fact] - public void DataStore_TryGetLocalBool_ReturnsFalse_WhenKeyAbsent() - { - var store = new InMemoryCrestronDataStore(); - - store.TryGetLocalBool("Missing", out var value).Should().BeFalse(); - value.Should().BeFalse(); - } - - [Fact] - public void DataStore_SetLocalUint_CanBeReadBackAsInt() - { - var store = new InMemoryCrestronDataStore(); - - store.SetLocalUint("UintKey", 3u).Should().BeTrue(); - store.TryGetLocalInt("UintKey", out var value).Should().BeTrue(); - value.Should().Be(3); - } - - [Fact] - public void DataStore_Seed_AllowsTestSetupOfReadPaths() - { - var store = new InMemoryCrestronDataStore(); - store.Seed("MyLevel", 2); - - store.TryGetLocalInt("MyLevel", out var level).Should().BeTrue(); - level.Should().Be(2); - } - - // ----------------------------------------------------------------------- - // DebugServiceRegistration - // ----------------------------------------------------------------------- - - [Fact] - public void ServiceRegistration_Register_StoresAllThreeServices() - { - var env = new FakeCrestronEnvironment(); - var console = new NoOpCrestronConsole(); - var store = new InMemoryCrestronDataStore(); - - DebugServiceRegistration.Register(env, console, store); - - DebugServiceRegistration.Environment.Should().BeSameAs(env); - DebugServiceRegistration.Console.Should().BeSameAs(console); - DebugServiceRegistration.DataStore.Should().BeSameAs(store); - } - - [Fact] - public void ServiceRegistration_Register_AcceptsNullsWithoutThrowing() - { - var act = () => DebugServiceRegistration.Register(null, null, null); - act.Should().NotThrow(); - } - - // ----------------------------------------------------------------------- - // ICrestronEnvironment — FakeCrestronEnvironment - // ----------------------------------------------------------------------- - - [Fact] - public void FakeEnvironment_DefaultsToAppliance() - { - var env = new FakeCrestronEnvironment(); - env.DevicePlatform.Should().Be(DevicePlatform.Appliance); - } - - [Fact] - public void FakeEnvironment_RaiseProgramStatus_FiresEvent() - { - var env = new FakeCrestronEnvironment(); - ProgramStatusEventType? received = null; - env.ProgramStatusChanged += (_, e) => received = e.EventType; - - env.RaiseProgramStatus(ProgramStatusEventType.Stopping); - - received.Should().Be(ProgramStatusEventType.Stopping); - } - - [Fact] - public void FakeEnvironment_RaiseEthernetEvent_FiresEvent() - { - var env = new FakeCrestronEnvironment(); - EthernetEventType? received = null; - env.EthernetEventReceived += (_, e) => received = e.EthernetEventType; - - env.RaiseEthernetEvent(EthernetEventType.LinkUp, adapter: 0); - - received.Should().Be(EthernetEventType.LinkUp); - } - - // ----------------------------------------------------------------------- - // ICrestronConsole — CapturingCrestronConsole - // ----------------------------------------------------------------------- - - [Fact] - public void CapturingConsole_PrintLine_CapturesMessage() - { - var console = new CapturingCrestronConsole(); - - console.PrintLine("hello world"); - - console.Lines.Should().ContainSingle().Which.Should().Be("hello world"); - } - - [Fact] - public void CapturingConsole_AddNewConsoleCommand_RecordsCommandName() - { - var console = new CapturingCrestronConsole(); - - console.AddNewConsoleCommand(_ => { }, "appdebug", "Sets debug level", ConsoleAccessLevel.AccessOperator); - - console.RegisteredCommands.Should().ContainSingle() - .Which.Command.Should().Be("appdebug"); - } -} diff --git a/src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj b/src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj deleted file mode 100644 index dd131cf1..00000000 --- a/src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - net9.0 - enable - enable - false - false - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - diff --git a/src/PepperDash.Core.Tests/TestInitializer.cs b/src/PepperDash.Core.Tests/TestInitializer.cs deleted file mode 100644 index 6455ea59..00000000 --- a/src/PepperDash.Core.Tests/TestInitializer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Runtime.CompilerServices; -using PepperDash.Core.Abstractions; -using PepperDash.Core.Tests.Fakes; - -namespace PepperDash.Core.Tests; - -/// -/// Runs once before any type in this assembly is accessed. -/// Registers fake Crestron service implementations with -/// so that the Debug static constructor uses them instead of the real Crestron SDK. -/// This must remain a module initializer (not a test fixture) because the static constructor -/// fires the first time any type in PepperDash.Core is referenced — before xUnit -/// has a chance to run fixture setup code. -/// -internal static class TestInitializer -{ - [ModuleInitializer] - internal static void Initialize() - { - DebugServiceRegistration.Register( - new FakeCrestronEnvironment - { - DevicePlatform = DevicePlatform.Server, // avoids any appliance-only code paths - RuntimeEnvironment = RuntimeEnvironment.Other, // skips console command registration - }, - new NoOpCrestronConsole(), - new InMemoryCrestronDataStore()); - } -} diff --git a/src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs b/src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs deleted file mode 100644 index 8c4a6faf..00000000 --- a/src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Crestron.SimplSharp; -using PdCore = PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Adapters; - -/// -/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK. -/// -public sealed class CrestronConsoleAdapter : PdCore.ICrestronConsole -{ - public void PrintLine(string message) => CrestronConsole.PrintLine(message); - - public void Print(string message) => CrestronConsole.Print(message); - - public void ConsoleCommandResponse(string message) => - CrestronConsole.ConsoleCommandResponse(message); - - public void AddNewConsoleCommand( - Action callback, - string command, - string helpText, - PdCore.ConsoleAccessLevel accessLevel) - { - var crestronLevel = accessLevel switch - { - PdCore.ConsoleAccessLevel.AccessAdministrator => ConsoleAccessLevelEnum.AccessAdministrator, - PdCore.ConsoleAccessLevel.AccessProgrammer => ConsoleAccessLevelEnum.AccessProgrammer, - _ => ConsoleAccessLevelEnum.AccessOperator, - }; - - // Wrap Action in a lambda — Crestron's delegate is not a standard Action. - CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel); - } -} diff --git a/src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs b/src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs deleted file mode 100644 index effed284..00000000 --- a/src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Crestron.SimplSharp.CrestronDataStore; -using PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Adapters; - -/// -/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK. -/// -public sealed class CrestronDataStoreAdapter : ICrestronDataStore -{ - public void InitStore() => CrestronDataStoreStatic.InitCrestronDataStore(); - - public bool TryGetLocalInt(string key, out int value) - { - var err = CrestronDataStoreStatic.GetLocalIntValue(key, out value); - return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } - - public bool SetLocalInt(string key, int value) - { - var err = CrestronDataStoreStatic.SetLocalIntValue(key, value); - return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } - - public bool SetLocalUint(string key, uint value) - { - var err = CrestronDataStoreStatic.SetLocalUintValue(key, value); - return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } - - public bool TryGetLocalBool(string key, out bool value) - { - var err = CrestronDataStoreStatic.GetLocalBoolValue(key, out value); - return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } - - public bool SetLocalBool(string key, bool value) - { - var err = CrestronDataStoreStatic.SetLocalBoolValue(key, value); - return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } -} diff --git a/src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs b/src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs deleted file mode 100644 index 62002d73..00000000 --- a/src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using Crestron.SimplSharp; -using PdCore = PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Adapters; - -/// -/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK. -/// -public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment -{ - // Subscribe once in constructor and re-raise as our event types. - private event EventHandler? _programStatusChanged; - private event EventHandler? _ethernetEventReceived; - - public CrestronEnvironmentAdapter() - { - CrestronEnvironment.ProgramStatusEventHandler += type => - _programStatusChanged?.Invoke(this, new PdCore.ProgramStatusEventArgs(MapProgramStatus(type))); - - CrestronEnvironment.EthernetEventHandler += args => - _ethernetEventReceived?.Invoke(this, new PdCore.PepperDashEthernetEventArgs( - args.EthernetEventType == eEthernetEventType.LinkDown - ? PdCore.EthernetEventType.LinkDown - : PdCore.EthernetEventType.LinkUp, - (short)args.EthernetAdapter)); - } - - public PdCore.DevicePlatform DevicePlatform => - CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance - ? PdCore.DevicePlatform.Appliance - : PdCore.DevicePlatform.Server; - - public PdCore.RuntimeEnvironment RuntimeEnvironment => - CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro - ? PdCore.RuntimeEnvironment.SimplSharpPro - : PdCore.RuntimeEnvironment.Other; - - public string NewLine => CrestronEnvironment.NewLine; - - public uint ApplicationNumber => InitialParametersClass.ApplicationNumber; - - public uint RoomId => uint.TryParse(InitialParametersClass.RoomId, out var r) ? r : 0; - - public event EventHandler ProgramStatusChanged - { - add => _programStatusChanged += value; - remove => _programStatusChanged -= value; - } - - public event EventHandler EthernetEventReceived - { - add => _ethernetEventReceived += value; - remove => _ethernetEventReceived -= value; - } - - public string GetApplicationRootDirectory() => - Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory(); - - /// - public bool IsHardwareRuntime => true; - - private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) => - type switch - { - eProgramStatusEventType.Stopping => PdCore.ProgramStatusEventType.Stopping, - eProgramStatusEventType.Paused => PdCore.ProgramStatusEventType.Paused, - eProgramStatusEventType.Resumed => PdCore.ProgramStatusEventType.Resumed, - _ => PdCore.ProgramStatusEventType.Starting, - }; -} diff --git a/src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs b/src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs deleted file mode 100644 index f2c80e5b..00000000 --- a/src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Crestron.SimplSharp; -using PepperDash.Core.Abstractions; - -namespace PepperDash.Core.Adapters; - -/// -/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK. -/// -public sealed class CrestronEthernetAdapter : IEthernetHelper -{ - public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId) - { - var crestronParam = parameter switch - { - EthernetParameterType.GetCurrentIpAddress => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, - EthernetParameterType.GetHostname => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, - EthernetParameterType.GetDomainName => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, - EthernetParameterType.GetLinkStatus => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS, - EthernetParameterType.GetCurrentDhcpState => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, - EthernetParameterType.GetCurrentIpMask => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, - EthernetParameterType.GetCurrentRouter => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, - EthernetParameterType.GetMacAddress => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, - EthernetParameterType.GetDnsServer => - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, - _ => throw new ArgumentOutOfRangeException(nameof(parameter), parameter, null), - }; - - return CrestronEthernetHelper.GetEthernetParameter(crestronParam, ethernetAdapterId); - } -} diff --git a/src/PepperDash.Core/ComTextHelper.cs b/src/PepperDash.Core/ComTextHelper.cs new file mode 100644 index 00000000..28a76975 --- /dev/null +++ b/src/PepperDash.Core/ComTextHelper.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace PepperDash.Core +{ + /// + /// Helper class for formatting communication text and byte data for debugging purposes. + /// + public class ComTextHelper + { + /// + /// Gets escaped text for a byte array + /// + /// + /// string with all bytes escaped + public static string GetEscapedText(byte[] bytes) + { + return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + /// + /// Gets escaped text for a string + /// + /// + /// string with all bytes escaped + public static string GetEscapedText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + /// + /// Gets debug text for a string + /// + /// + /// string with all non-printable characters escaped + public static string GetDebugText(string text) + { + return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/CommunicationGather.cs b/src/PepperDash.Core/Comm/CommunicationGather.cs index 4131d364..e75a012d 100644 --- a/src/PepperDash.Core/Comm/CommunicationGather.cs +++ b/src/PepperDash.Core/Comm/CommunicationGather.cs @@ -8,8 +8,8 @@ using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Core; - +namespace PepperDash.Core +{ /// /// Defines the string event handler for line events on the gather /// @@ -30,7 +30,7 @@ namespace PepperDash.Core; /// /// The communication port that this gathers on /// - public ICommunicationReceiver Port { get; private set; } + public ICommunicationReceiver Port { get; private set; } /// /// Default false. If true, the delimiter will be included in the line output @@ -67,22 +67,22 @@ namespace PepperDash.Core; /// /// /// - public CommunicationGather(ICommunicationReceiver port, string delimiter) - :this(port, new string[] { delimiter} ) + public CommunicationGather(ICommunicationReceiver port, string delimiter) + :this(port, new string[] { delimiter} ) { } - /// - /// Constructor for using an array of string delimiters - /// - /// - /// - public CommunicationGather(ICommunicationReceiver port, string[] delimiters) - { - Port = port; - StringDelimiters = delimiters; - port.TextReceived += Port_TextReceivedStringDelimiter; - } + /// + /// Constructor for using an array of string delimiters + /// + /// + /// + public CommunicationGather(ICommunicationReceiver port, string[] delimiters) + { + Port = port; + StringDelimiters = delimiters; + port.TextReceived += Port_TextReceivedStringDelimiter; + } /// /// Stop method @@ -135,35 +135,35 @@ namespace PepperDash.Core; ReceiveBuffer.Append(args.Text); var str = ReceiveBuffer.ToString(); - // Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a + // Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a - // RX: DEV - // Split: (1) "DEV" - // RX: I - // Split: (1) "DEVI" - // RX: CE get version - // Split: (1) "DEVICE get version" - // RX: \x0d\x0a+OK "value":"1234"\x0d\x0a - // Split: (2) DEVICE get version, +OK "value":"1234" + // RX: DEV + // Split: (1) "DEV" + // RX: I + // Split: (1) "DEVI" + // RX: CE get version + // Split: (1) "DEVICE get version" + // RX: \x0d\x0a+OK "value":"1234"\x0d\x0a + // Split: (2) DEVICE get version, +OK "value":"1234" - // Iterate the delimiters and fire an event for any matching delimiter - foreach (var delimiter in StringDelimiters) - { - var lines = Regex.Split(str, delimiter); - if (lines.Length == 1) - continue; - - for (int i = 0; i < lines.Length - 1; i++) + // Iterate the delimiters and fire an event for any matching delimiter + foreach (var delimiter in StringDelimiters) { - string strToSend = null; - if (IncludeDelimiter) - strToSend = lines[i] + delimiter; - else - strToSend = lines[i]; - handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter)); + var lines = Regex.Split(str, delimiter); + if (lines.Length == 1) + continue; + + for (int i = 0; i < lines.Length - 1; i++) + { + string strToSend = null; + if (IncludeDelimiter) + strToSend = lines[i] + delimiter; + else + strToSend = lines[i]; + handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter)); + } + ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); } - ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); - } } } @@ -174,4 +174,5 @@ namespace PepperDash.Core; { Stop(); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs b/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs index d2da3257..780e65d0 100644 --- a/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs +++ b/src/PepperDash.Core/Comm/CommunicationStreamDebugging.cs @@ -1,159 +1,138 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; +using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Core; - -/// -/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable -/// -public class CommunicationStreamDebugging +namespace PepperDash.Core { /// - /// Device Key that this instance configures + /// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable /// - public string ParentDeviceKey { get; private set; } - - /// - /// Timer to disable automatically if not manually disabled - /// - private Timer DebugExpiryPeriod; - - /// - /// The current debug setting - /// - public eStreamDebuggingSetting DebugSetting { get; private set; } - - private uint _DebugTimeoutInMs; - private const uint _DefaultDebugTimeoutMin = 30; - - /// - /// Timeout in Minutes - /// - public uint DebugTimeoutMinutes + public class CommunicationStreamDebugging { - get + /// + /// Device Key that this instance configures + /// + public string ParentDeviceKey { get; private set; } + + /// + /// Timer to disable automatically if not manually disabled + /// + private CTimer DebugExpiryPeriod; + + /// + /// Gets or sets the DebugSetting + /// + public eStreamDebuggingSetting DebugSetting { get; private set; } + + private uint _DebugTimeoutInMs; + private const uint _DefaultDebugTimeoutMin = 30; + + /// + /// Timeout in Minutes + /// + public uint DebugTimeoutMinutes { - return _DebugTimeoutInMs/60000; - } - } - - /// - /// Indicates that receive stream debugging is enabled - /// - public bool RxStreamDebuggingIsEnabled{ get; private set; } - - /// - /// Indicates that transmit stream debugging is enabled - /// - public bool TxStreamDebuggingIsEnabled { get; private set; } - - /// - /// Constructor - /// - /// - public CommunicationStreamDebugging(string parentDeviceKey) - { - ParentDeviceKey = parentDeviceKey; - } - - - /// - /// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues - /// - /// - public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting) - { - if (setting == eStreamDebuggingSetting.Off) - { - DisableDebugging(); - return; + get + { + return _DebugTimeoutInMs / 60000; + } } - SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin); - } + /// + /// Gets or sets the RxStreamDebuggingIsEnabled + /// + public bool RxStreamDebuggingIsEnabled { get; private set; } - /// - /// Sets the debugging setting for the specified number of minutes - /// - /// - /// - public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes) - { - if (setting == eStreamDebuggingSetting.Off) + /// + /// Indicates that transmit stream debugging is enabled + /// + public bool TxStreamDebuggingIsEnabled { get; private set; } + + /// + /// Constructor + /// + /// + public CommunicationStreamDebugging(string parentDeviceKey) { - DisableDebugging(); - return; + ParentDeviceKey = parentDeviceKey; } - _DebugTimeoutInMs = minutes * 60000; - StopDebugTimer(); - - DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false }; - DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging(); - DebugExpiryPeriod.Start(); - - if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx) - RxStreamDebuggingIsEnabled = true; - - if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx) - TxStreamDebuggingIsEnabled = true; - - Debug.SetDeviceDebugSettings(ParentDeviceKey, setting); - - } - - /// - /// Disabled debugging - /// - private void DisableDebugging() - { - StopDebugTimer(); - - Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off); - } - - private void StopDebugTimer() - { - RxStreamDebuggingIsEnabled = false; - TxStreamDebuggingIsEnabled = false; - - if (DebugExpiryPeriod == null) + /// + /// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues + /// + /// + /// + /// SetDebuggingWithDefaultTimeout method + /// + public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting) { - return; + if (setting == eStreamDebuggingSetting.Off) + { + DisableDebugging(); + return; + } + + SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin); } - DebugExpiryPeriod.Stop(); - DebugExpiryPeriod.Dispose(); - DebugExpiryPeriod = null; + /// + /// Sets the debugging setting for the specified number of minutes + /// + /// + /// + /// + /// SetDebuggingWithSpecificTimeout method + /// + public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes) + { + if (setting == eStreamDebuggingSetting.Off) + { + DisableDebugging(); + return; + } + + _DebugTimeoutInMs = minutes * 60000; + + StopDebugTimer(); + + DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs); + + if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx) + RxStreamDebuggingIsEnabled = true; + + if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx) + TxStreamDebuggingIsEnabled = true; + + Debug.SetDeviceDebugSettings(ParentDeviceKey, setting); + + } + + /// + /// Disabled debugging + /// + private void DisableDebugging() + { + StopDebugTimer(); + + Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off); + } + + private void StopDebugTimer() + { + RxStreamDebuggingIsEnabled = false; + TxStreamDebuggingIsEnabled = false; + + if (DebugExpiryPeriod == null) + { + return; + } + + DebugExpiryPeriod.Stop(); + DebugExpiryPeriod.Dispose(); + DebugExpiryPeriod = null; + } } } - -/// -/// The available settings for stream debugging -/// -[Flags] -public enum eStreamDebuggingSetting -{ - /// - /// Debug off - /// - Off = 0, - /// - /// Debug received data - /// - Rx = 1, - /// - /// Debug transmitted data - /// - Tx = 2, - /// - /// Debug both received and transmitted data - /// - Both = Rx | Tx -} - diff --git a/src/PepperDash.Core/Comm/ControlPropertiesConfig.cs b/src/PepperDash.Core/Comm/ControlPropertiesConfig.cs index e359eade..c665dad8 100644 --- a/src/PepperDash.Core/Comm/ControlPropertiesConfig.cs +++ b/src/PepperDash.Core/Comm/ControlPropertiesConfig.cs @@ -1,97 +1,93 @@ -extern alias NewtonsoftJson; - -using System; +using System; using Crestron.SimplSharp; -using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute; -using JsonIgnore = NewtonsoftJson::Newtonsoft.Json.JsonIgnoreAttribute; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; -using NullValueHandling = NewtonsoftJson::Newtonsoft.Json.NullValueHandling; -using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; -namespace PepperDash.Core; - -/// -/// Config properties that indicate how to communicate with a device for control -/// -public class ControlPropertiesConfig +namespace PepperDash.Core { /// - /// The method of control + /// Represents a ControlPropertiesConfig /// - [JsonProperty("method")] - [JsonConverter(typeof(StringEnumConverter))] - public eControlMethod Method { get; set; } + public class ControlPropertiesConfig + { + /// + /// The method of control + /// + [JsonProperty("method")] + [JsonConverter(typeof(StringEnumConverter))] + public eControlMethod Method { get; set; } - /// - /// The key of the device that contains the control port - /// - [JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)] - public string ControlPortDevKey { get; set; } + /// + /// The key of the device that contains the control port + /// + [JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)] + public string ControlPortDevKey { get; set; } - /// - /// The number of the control port on the device specified by ControlPortDevKey - /// - [JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value - public uint? ControlPortNumber { get; set; } + /// + /// The number of the control port on the device specified by ControlPortDevKey + /// + [JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value + public uint? ControlPortNumber { get; set; } - /// - /// The name of the control port on the device specified by ControlPortDevKey - /// - [JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value - public string ControlPortName { get; set; } + /// + /// The name of the control port on the device specified by ControlPortDevKey + /// + [JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value + public string ControlPortName { get; set; } - /// - /// Properties for ethernet based communications - /// - [JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)] - public TcpSshPropertiesConfig TcpSshProperties { get; set; } + /// + /// Properties for ethernet based communications + /// + [JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)] + public TcpSshPropertiesConfig TcpSshProperties { get; set; } - /// - /// The filename and path for the IR file - /// - [JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)] - public string IrFile { get; set; } + /// + /// The filename and path for the IR file + /// + [JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)] + public string IrFile { get; set; } - /// - /// The IpId of a Crestron device - /// - [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] - public string IpId { get; set; } + /// + /// The IpId of a Crestron device + /// + [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] + public string IpId { get; set; } - /// - /// Readonly uint representation of the IpId - /// - [JsonIgnore] - public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } } + /// + /// Readonly uint representation of the IpId + /// + [JsonIgnore] + public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } } - /// - /// Char indicating end of line - /// - [JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)] - public char EndOfLineChar { get; set; } + /// + /// Char indicating end of line + /// + [JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)] + public char EndOfLineChar { get; set; } - /// - /// Defaults to Environment.NewLine; - /// - [JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)] - public string EndOfLineString { get; set; } + /// + /// Defaults to Environment.NewLine; + /// + [JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)] + public string EndOfLineString { get; set; } - /// - /// Indicates - /// - [JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)] - public string DeviceReadyResponsePattern { get; set; } + /// + /// Indicates + /// + [JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)] + public string DeviceReadyResponsePattern { get; set; } - /// - /// Used when communcating to programs running in VC-4 - /// - [JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)] - public string RoomId { get; set; } + /// + /// Used when communcating to programs running in VC-4 + /// + [JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)] + public string RoomId { get; set; } - /// - /// Constructor - /// - public ControlPropertiesConfig() - { + /// + /// Constructor + /// + public ControlPropertiesConfig() + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/EventArgs.cs b/src/PepperDash.Core/Comm/EventArgs.cs index d68dda7c..0b394423 100644 --- a/src/PepperDash.Core/Comm/EventArgs.cs +++ b/src/PepperDash.Core/Comm/EventArgs.cs @@ -16,237 +16,236 @@ using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -namespace PepperDash.Core; - -/// -/// Delegate for notifying of socket status changes -/// -/// -public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client); - -/// -/// EventArgs class for socket status changes -/// -public class GenericSocketStatusChageEventArgs : EventArgs +namespace PepperDash.Core { /// - /// Gets or sets the Client - /// - public ISocketStatus Client { get; private set; } - - /// - /// Constructor + /// Delegate for notifying of socket status changes /// /// - public GenericSocketStatusChageEventArgs(ISocketStatus client) - { - Client = client; + public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client); + + /// + /// EventArgs class for socket status changes + /// + public class GenericSocketStatusChageEventArgs : EventArgs + { + /// + /// Gets or sets the Client + /// + public ISocketStatus Client { get; private set; } + + /// + /// + /// + /// + public GenericSocketStatusChageEventArgs(ISocketStatus client) + { + Client = client; + } + /// + /// S+ Constructor + /// + public GenericSocketStatusChageEventArgs() { } } - /// - /// S+ Constructor - /// - public GenericSocketStatusChageEventArgs() { } -} - -/// -/// Delegate for notifying of TCP Server state changes -/// -/// -public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state); - -/// -/// EventArgs class for TCP Server state changes -/// -public class GenericTcpServerStateChangedEventArgs : EventArgs -{ - /// - /// Gets or sets the State - /// - public ServerState State { get; private set; } /// - /// Constructor + /// Delegate for notifying of TCP Server state changes /// /// - public GenericTcpServerStateChangedEventArgs(ServerState state) + public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state); + + /// + /// EventArgs class for TCP Server state changes + /// + public class GenericTcpServerStateChangedEventArgs : EventArgs { - State = state; - } - /// - /// S+ Constructor - /// - public GenericTcpServerStateChangedEventArgs() { } -} + /// + /// Gets or sets the State + /// + public ServerState State { get; private set; } -/// -/// Delegate for TCP Server socket status changes -/// -/// -/// -/// -public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus); -/// -/// EventArgs for TCP server socket status changes -/// -public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs -{ - /// - /// Gets or sets the Socket - /// - public object Socket { get; private set; } - - /// - /// Gets or sets the index of the client from which the status change was received - /// - public uint ReceivedFromClientIndex { get; private set; } - - /// - /// Gets or sets the ClientStatus - /// - public SocketStatus ClientStatus { get; set; } - - /// - /// Constructor - /// - /// - /// - public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus) - { - Socket = socket; - ClientStatus = clientStatus; - } - - /// - /// Constructor - /// - /// - /// - /// - public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus) - { - Socket = socket; - ReceivedFromClientIndex = clientIndex; - ClientStatus = clientStatus; - } - /// - /// S+ Constructor - /// - public GenericTcpServerSocketStatusChangeEventArgs() { } -} - -/// -/// EventArgs for TCP server com method receive text -/// -public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs -{ - /// - /// Gets or sets the index of the client from which the text was received - /// - public uint ReceivedFromClientIndex { get; private set; } - - /// - /// Gets the index of the client from which the text was received as a ushort - /// - public ushort ReceivedFromClientIndexShort - { - get + /// + /// + /// + /// + public GenericTcpServerStateChangedEventArgs(ServerState state) { - return (ushort)ReceivedFromClientIndex; + State = state; } + /// + /// S+ Constructor + /// + public GenericTcpServerStateChangedEventArgs() { } } /// - /// Gets or sets the Text + /// Delegate for TCP Server socket status changes /// - public string Text { get; private set; } - - /// - /// Constructor - /// - /// - public GenericTcpServerCommMethodReceiveTextArgs(string text) - { - Text = text; - } - - /// - /// Constructor - /// - /// + /// /// - public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex) + /// + public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus); + /// + /// EventArgs for TCP server socket status changes + /// + public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs { - Text = text; - ReceivedFromClientIndex = clientIndex; - } - /// - /// S+ Constructor - /// - public GenericTcpServerCommMethodReceiveTextArgs() { } -} + /// + /// + /// + public object Socket { get; private set; } + /// + /// + /// + public uint ReceivedFromClientIndex { get; private set; } + /// + /// + /// + public SocketStatus ClientStatus { get; set; } -/// -/// EventArgs for TCP server client ready for communication -/// -public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs -{ - /// - /// Gets or sets IsReady - /// - public bool IsReady; + /// + /// + /// + /// + /// + public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus) + { + Socket = socket; + ClientStatus = clientStatus; + } - /// - /// Constructor - /// - /// - public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady) - { - IsReady = isReady; + /// + /// + /// + /// + /// + /// + public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus) + { + Socket = socket; + ReceivedFromClientIndex = clientIndex; + ClientStatus = clientStatus; + } + /// + /// S+ Constructor + /// + public GenericTcpServerSocketStatusChangeEventArgs() { } } /// - /// S+ Constructor + /// EventArgs for TCP server com method receive text /// - public GenericTcpServerClientReadyForcommunicationsEventArgs() { } -} - -/// -/// EventArgs for UDP connected -/// -public class GenericUdpConnectedEventArgs : EventArgs -{ - /// - /// Gets or sets the UConnected - /// - public ushort UConnected; - - /// - /// Gets or sets the Connected status - /// - public bool Connected; - - /// - /// Constructor - /// - public GenericUdpConnectedEventArgs() { } - - /// - /// Constructor - /// - /// - public GenericUdpConnectedEventArgs(ushort uconnected) + public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs { - UConnected = uconnected; + /// + /// + /// + public uint ReceivedFromClientIndex { get; private set; } + + /// + /// + /// + public ushort ReceivedFromClientIndexShort + { + get + { + return (ushort)ReceivedFromClientIndex; + } + } + + /// + /// Gets or sets the Text + /// + public string Text { get; private set; } + + /// + /// + /// + /// + public GenericTcpServerCommMethodReceiveTextArgs(string text) + { + Text = text; + } + + /// + /// + /// + /// + /// + public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex) + { + Text = text; + ReceivedFromClientIndex = clientIndex; + } + /// + /// S+ Constructor + /// + public GenericTcpServerCommMethodReceiveTextArgs() { } } /// - /// Constructor + /// EventArgs for TCP server client ready for communication /// - /// - public GenericUdpConnectedEventArgs(bool connected) + public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs { - Connected = connected; + /// + /// + /// + public bool IsReady; + + /// + /// + /// + /// + public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady) + { + IsReady = isReady; + } + /// + /// S+ Constructor + /// + public GenericTcpServerClientReadyForcommunicationsEventArgs() { } } -} + /// + /// EventArgs for UDP connected + /// + public class GenericUdpConnectedEventArgs : EventArgs + { + /// + /// + /// + public ushort UConnected; + /// + /// + /// + public bool Connected; + /// + /// Constructor + /// + public GenericUdpConnectedEventArgs() { } + + /// + /// + /// + /// + public GenericUdpConnectedEventArgs(ushort uconnected) + { + UConnected = uconnected; + } + + /// + /// + /// + /// + public GenericUdpConnectedEventArgs(bool connected) + { + Connected = connected; + } + + } + + + +} \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs index c988db02..204c548b 100644 --- a/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs @@ -3,960 +3,956 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; -namespace PepperDash.Core; - -/// -/// A class to handle secure TCP/IP communications with a server -/// -public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect +namespace PepperDash.Core { - private const string SplusKey = "Uninitialized Secure Tcp _client"; /// - /// Stream debugging + /// A class to handle secure TCP/IP communications with a server /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; - - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; - - #region GenericSecureTcpIpClient Events & Delegates - - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; - - /// - /// Auto reconnect evant handler - /// - public event EventHandler AutoReconnectTriggered; - - /// - /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. - /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. - /// - public event EventHandler TextReceivedQueueInvoke; - - /// - /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require - /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. - /// - public event EventHandler ClientReadyForCommunications; - - #endregion - - - #region GenricTcpIpClient properties - - private string _hostname; - - /// - /// Address of server - /// - public string Hostname + public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { - get { return _hostname; } - set + private const string SplusKey = "Uninitialized Secure Tcp _client"; + /// + /// Stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; + + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; + + #region GenericSecureTcpIpClient Events & Delegates + + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; + + /// + /// Auto reconnect evant handler + /// + public event EventHandler AutoReconnectTriggered; + + /// + /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. + /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. + /// + public event EventHandler TextReceivedQueueInvoke; + + /// + /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require + /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. + /// + public event EventHandler ClientReadyForCommunications; + + #endregion + + + #region GenricTcpIpClient properties + + private string _hostname; + + /// + /// Address of server + /// + public string Hostname { - _hostname = value; - if (_client != null) + get { return _hostname; } + set { - _client.AddressClientConnectedTo = _hostname; - } - } - } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// S+ helper - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Internal secure client - /// - private SecureTCPClient _client; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Flag Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - // private Timer for auto reconnect - private Timer RetryTimer; - - #endregion - - #region GenericSecureTcpIpClient properties - - /// - /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module - /// - public string SharedKey { get; set; } - - /// - /// flag to show the client is waiting for the server to send the shared key - /// - private bool WaitingForSharedKeyResponse { get; set; } - - /// - /// Semaphore on connect method - /// - bool IsTryingToConnect; - - /// - /// Bool showing if socket is ready for communication after shared key exchange - /// - public bool IsReadyForCommunication { get; set; } - - /// - /// S+ helper for IsReadyForCommunication - /// - public ushort UIsReadyForCommunication - { - get { return (ushort)(IsReadyForCommunication ? 1 : 0); } - } - - /// - /// Bool Heartbeat Enabled flag - /// - public bool HeartbeatEnabled { get; set; } - - /// - /// S+ helper for Heartbeat Enabled - /// - public ushort UHeartbeatEnabled - { - get { return (ushort)(HeartbeatEnabled ? 1 : 0); } - set { HeartbeatEnabled = value == 1; } - } - - /// - /// Heartbeat String - /// - public string HeartbeatString { get; set; } - //public int HeartbeatInterval = 50000; - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatInterval { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } } - - Timer HeartbeatSendTimer; - Timer HeartbeatAckTimer; - - // Used to force disconnection on a dead connect attempt - Timer ConnectFailTimer; - Timer WaitForSharedKey; - private int ConnectionCount; - - bool ProgramIsStopping; - - /// - /// Queue lock - /// - private readonly object _dequeueLock = new(); - - /// - /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before - /// calling initialize. - /// - public int ReceiveQueueSize { get; set; } - - /// - /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before - /// calling initialize. - /// - private CrestronQueue MessageQueue; - - #endregion - - #region Constructors - - /// - /// Constructor - /// - /// - /// - /// - /// - public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - Hostname = address; - Port = port; - BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - } - - /// - /// Contstructor that sets all properties by calling the initialize method with a config object. - /// - /// - /// - public GenericSecureTcpIpClient(string key, TcpClientConfigObject clientConfigObject) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - - Initialize(clientConfigObject); - } - - /// - /// Default constructor for S+ - /// - public GenericSecureTcpIpClient() - : base(SplusKey) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - } - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client. - /// - /// - public void Initialize(TcpClientConfigObject config) - { - if (config == null) - { - this.LogWarning( "Could not initialize client with key: {0}", Key); - return; - } - try - { - Hostname = config.Control.TcpSshProperties.Address; - Port = config.Control.TcpSshProperties.Port > 0 && config.Control.TcpSshProperties.Port <= 65535 - ? config.Control.TcpSshProperties.Port - : 80; - - AutoReconnect = config.Control.TcpSshProperties.AutoReconnect; - AutoReconnectIntervalMs = config.Control.TcpSshProperties.AutoReconnectIntervalMs > 1000 - ? config.Control.TcpSshProperties.AutoReconnectIntervalMs - : 5000; - - SharedKey = config.SharedKey; - SharedKeyRequired = config.SharedKeyRequired; - - HeartbeatEnabled = config.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = config.HeartbeatRequiredIntervalInSeconds > 0 - ? config.HeartbeatRequiredIntervalInSeconds - : (ushort)15; - - - HeartbeatString = string.IsNullOrEmpty(config.HeartbeatStringToMatch) - ? "heartbeat" - : config.HeartbeatStringToMatch; - - BufferSize = config.Control.TcpSshProperties.BufferSize > 2000 - ? config.Control.TcpSshProperties.BufferSize - : 2000; - - ReceiveQueueSize = config.ReceiveQueueSize > 20 - ? config.ReceiveQueueSize - : 20; - - MessageQueue = new CrestronQueue(ReceiveQueueSize); - } - catch (Exception ex) - { - this.LogError("Exception initializing client with key: {0}\rException: {1}", Key, ex); - } - } - - #endregion - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) - { - this.LogInformation("Program stopping. Closing _client connection"); - ProgramIsStopping = true; - Disconnect(); - } - - } - - /// - /// Deactivate the client - /// - /// - public override bool Deactivate() - { - if (_client != null) - { - _client.SocketStatusChange -= this.Client_SocketStatusChange; - DisconnectClient(); - } - return true; - } - - /// - /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. - /// - public void Connect() - { - ConnectionCount++; - this.LogVerbose("Attempting connect Count:{0}", ConnectionCount); - - - if (IsConnected) - { - this.LogInformation("Already connected. Ignoring."); - return; - } - if (IsTryingToConnect) - { - this.LogInformation("Already trying to connect. Ignoring."); - return; - } - try - { - IsTryingToConnect = true; - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - if (string.IsNullOrEmpty(Hostname)) - { - this.LogWarning("DynamicTcpClient: No address set"); - return; - } - if (Port < 1 || Port > 65535) - { - this.LogWarning("DynamicTcpClient: Invalid port"); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - this.LogWarning("DynamicTcpClient: No Shared Key set"); - return; - } - - // clean up previous client - if (_client != null) - { - Disconnect(); - } - DisconnectCalledByUser = false; - - _client = new SecureTCPClient(Hostname, Port, BufferSize); - _client.SocketStatusChange += Client_SocketStatusChange; - if (HeartbeatEnabled) - _client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); - _client.AddressClientConnectedTo = Hostname; - _client.PortNumber = Port; - // SecureClient = c; - - //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); - - ConnectFailTimer = new Timer(30000) { AutoReset = false }; - ConnectFailTimer.Elapsed += (s, e) => - { - this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); - if (IsTryingToConnect) + _hostname = value; + if (_client != null) { - IsTryingToConnect = false; - - //if (ConnectionHasHungCallback != null) - //{ - // ConnectionHasHungCallback(); - //} - //SecureClient.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); + _client.AddressClientConnectedTo = _hostname; } - }; - ConnectFailTimer.Start(); + } + } - this.LogVerbose("Making Connection Count:{0}", ConnectionCount); - _client.ConnectToServerAsync(o => + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// S+ helper + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Internal secure client + /// + private SecureTCPClient _client; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get { - this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } + } - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - } - IsTryingToConnect = false; + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } - if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - this.LogVerbose("_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); - o.ReceiveDataAsync(Receive); + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } - if (SharedKeyRequired) - { - WaitingForSharedKeyResponse = true; - WaitForSharedKey = new Timer(15000) { AutoReset = false }; - WaitForSharedKey.Elapsed += (s, e) => - { - this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); - // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup - o.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - //OnClientReadyForcommunications(false); // Should send false event - }; - WaitForSharedKey.Start(); - } - else - { - //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key - //required this is called by the shared key being negotiated - if (IsReadyForCommunication == false) - { - OnClientReadyForcommunications(true); // Key not required - } - } - } + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } + + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Flag Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + // private Timer for auto reconnect + private CTimer RetryTimer; + + #endregion + + #region GenericSecureTcpIpClient properties + + /// + /// Gets or sets the SharedKeyRequired + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; else - { - this.LogWarning("Connect attempt failed {0}", o.ClientStatus); - CheckClosedAndTryReconnect(); - } - }); - } - catch (Exception ex) - { - this.LogError("_client connection exception: {0}", ex.Message); - IsTryingToConnect = false; - CheckClosedAndTryReconnect(); - } - } - - /// - /// - /// - public void Disconnect() - { - this.LogVerbose("Disconnect Called"); - - DisconnectCalledByUser = true; - - // stop trying reconnects, if we are - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - - if (_client != null) - { - DisconnectClient(); - this.LogDebug("Disconnected"); - } - } - - /// - /// Does the actual disconnect business - /// - public void DisconnectClient() - { - if (_client == null) return; - - this.LogInformation("Disconnecting client"); - if (IsConnected) - _client.DisconnectFromServer(); - - // close up client. ALWAYS use this when disconnecting. - IsTryingToConnect = false; - - this.LogVerbose("Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : ""); - _client.SocketStatusChange -= Client_SocketStatusChange; - _client.Dispose(); - _client = null; - - if (ConnectFailTimer == null) return; - ConnectFailTimer.Stop(); - ConnectFailTimer.Dispose(); - ConnectFailTimer = null; - } - - #region Methods - - /// - /// Called from Connect failure or Socket Status change if - /// auto reconnect and socket disconnected (Not disconnected by user) - /// - void CheckClosedAndTryReconnect() - { - if (_client != null) - { - this.LogVerbose("Cleaning up remotely closed/failed connection."); - Disconnect(); - } - if (!DisconnectCalledByUser && AutoReconnect) - { - var halfInterval = AutoReconnectIntervalMs / 2; - var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; - this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime); - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; + SharedKeyRequired = false; } - if (AutoReconnectTriggered != null) - AutoReconnectTriggered(this, new EventArgs()); - RetryTimer = new Timer(rndTime) { AutoReset = false }; - RetryTimer.Elapsed += (s, e) => Connect(); - RetryTimer.Start(); } - } - /// - /// Receive callback - /// - /// - /// - void Receive(SecureTCPClient client, int numBytes) - { - if (numBytes > 0) + /// + /// Gets or sets the SharedKey + /// + public string SharedKey { get; set; } + + /// + /// flag to show the client is waiting for the server to send the shared key + /// + private bool WaitingForSharedKeyResponse { get; set; } + + /// + /// Semaphore on connect method + /// + bool IsTryingToConnect; + + /// + /// Gets or sets the IsReadyForCommunication + /// + public bool IsReadyForCommunication { get; set; } + + /// + /// S+ helper for IsReadyForCommunication + /// + public ushort UIsReadyForCommunication { - string str = string.Empty; - var handler = TextReceivedQueueInvoke; + get { return (ushort)(IsReadyForCommunication ? 1 : 0); } + } + + /// + /// Bool Heartbeat Enabled flag + /// + public bool HeartbeatEnabled { get; set; } + + /// + /// S+ helper for Heartbeat Enabled + /// + public ushort UHeartbeatEnabled + { + get { return (ushort)(HeartbeatEnabled ? 1 : 0); } + set { HeartbeatEnabled = value == 1; } + } + + /// + /// Heartbeat String + /// + public string HeartbeatString { get; set; } + //public int HeartbeatInterval = 50000; + + /// + /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ + /// + public int HeartbeatInterval { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } } + + CTimer HeartbeatSendTimer; + CTimer HeartbeatAckTimer; + + // Used to force disconnection on a dead connect attempt + CTimer ConnectFailTimer; + CTimer WaitForSharedKey; + private int ConnectionCount; + + bool ProgramIsStopping; + + /// + /// Queue lock + /// + CCriticalSection DequeueLock = new CCriticalSection(); + + /// + /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before + /// calling initialize. + /// + public int ReceiveQueueSize { get; set; } + + /// + /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before + /// calling initialize. + /// + private CrestronQueue MessageQueue; + + #endregion + + #region Constructors + + /// + /// Constructor + /// + /// + /// + /// + /// + public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + Hostname = address; + Port = port; + BufferSize = bufferSize; + AutoReconnectIntervalMs = 5000; + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + } + + /// + /// Contstructor that sets all properties by calling the initialize method with a config object. + /// + /// + /// + public GenericSecureTcpIpClient(string key, TcpClientConfigObject clientConfigObject) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + Initialize(clientConfigObject); + } + + /// + /// Default constructor for S+ + /// + public GenericSecureTcpIpClient() + : base(SplusKey) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + } + + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client. + /// + /// + public void Initialize(TcpClientConfigObject config) + { + if (config == null) + { + Debug.Console(0, this, "Could not initialize client with key: {0}", Key); + return; + } try { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - this.LogVerbose("_client Received:\r--------\r{0}\r--------", str); - if (!string.IsNullOrEmpty(checkHeartbeat(str))) - { + Hostname = config.Control.TcpSshProperties.Address; + Port = config.Control.TcpSshProperties.Port > 0 && config.Control.TcpSshProperties.Port <= 65535 + ? config.Control.TcpSshProperties.Port + : 80; - if (SharedKeyRequired && str == "SharedKey:") - { - this.LogVerbose("Server asking for shared key, sending"); - SendText(SharedKey + "\n"); - } - else if (SharedKeyRequired && str == "Shared Key Match") - { - StopWaitForSharedKeyTimer(); + AutoReconnect = config.Control.TcpSshProperties.AutoReconnect; + AutoReconnectIntervalMs = config.Control.TcpSshProperties.AutoReconnectIntervalMs > 1000 + ? config.Control.TcpSshProperties.AutoReconnectIntervalMs + : 5000; + + SharedKey = config.SharedKey; + SharedKeyRequired = config.SharedKeyRequired; + + HeartbeatEnabled = config.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = config.HeartbeatRequiredIntervalInSeconds > 0 + ? config.HeartbeatRequiredIntervalInSeconds + : (ushort)15; - this.LogVerbose("Shared key confirmed. Ready for communication"); - OnClientReadyForcommunications(true); // Successful key exchange - } - else - { - //var bytesHandler = BytesReceived; - //if (bytesHandler != null) - // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - if (handler != null) - { - MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str)); - } - } - } + HeartbeatString = string.IsNullOrEmpty(config.HeartbeatStringToMatch) + ? "heartbeat" + : config.HeartbeatStringToMatch; + + BufferSize = config.Control.TcpSshProperties.BufferSize > 2000 + ? config.Control.TcpSshProperties.BufferSize + : 2000; + + ReceiveQueueSize = config.ReceiveQueueSize > 20 + ? config.ReceiveQueueSize + : 20; + + MessageQueue = new CrestronQueue(ReceiveQueueSize); } catch (Exception ex) { - this.LogError("Error receiving data: {1}. Error: {0}", str, ex.Message); + Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex); } - if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - client.ReceiveDataAsync(Receive); + } - //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. - if (handler != null) + #endregion + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) { - if (System.Threading.Monitor.TryEnter(_dequeueLock)) - System.Threading.Tasks.Task.Run(() => DequeueEvent()); + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing _client connection"); + ProgramIsStopping = true; + Disconnect(); } - } - else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync - { - client.DisconnectFromServer(); - } - } - /// - /// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently. - /// It will dequeue items as they are enqueued automatically. - /// - void DequeueEvent() - { - try + } + + /// + /// Deactivate the client + /// + /// + /// + /// Deactivate method + /// + public override bool Deactivate() { - while (true) + if (_client != null) { - // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. - var message = MessageQueue.Dequeue(); - var handler = TextReceivedQueueInvoke; - if (handler != null) - { - handler(this, message); - } - } - } - catch (Exception e) - { - this.LogError(e, "DequeueEvent error: {0}", e.Message); - } - // Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it. - System.Threading.Monitor.Exit(_dequeueLock); - } - - void HeartbeatStart() - { - if (HeartbeatEnabled) - { - this.LogVerbose("Starting Heartbeat"); - if (HeartbeatSendTimer == null) - { - - HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true }; - HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null); - HeartbeatSendTimer.Start(); - } - if (HeartbeatAckTimer == null) - { - HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); + _client.SocketStatusChange -= this.Client_SocketStatusChange; + DisconnectClient(); } + return true; } - } - void HeartbeatStop() - { - - if (HeartbeatSendTimer != null) + /// + /// Connect method + /// + public void Connect() { - this.LogVerbose("Stoping Heartbeat Send"); - HeartbeatSendTimer.Stop(); - HeartbeatSendTimer = null; - } - if (HeartbeatAckTimer != null) - { - this.LogVerbose("Stoping Heartbeat Ack"); - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer = null; - } + ConnectionCount++; + Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); - } - void SendHeartbeat(object notused) - { - this.SendText(HeartbeatString); - this.LogVerbose("Sending Heartbeat"); - - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(string received) - { - try - { - if (HeartbeatEnabled) - { - if (!string.IsNullOrEmpty(HeartbeatString)) - { - var remainingText = received.Replace(HeartbeatString, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatString)) - { - if (HeartbeatAckTimer != null) - { - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer.Interval = HeartbeatInterval * 2; - HeartbeatAckTimer.Start(); - } - else - { - HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); - } - this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString); - return remainingText; - } - } - } - } - catch (Exception ex) - { - this.LogError(ex, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - - - void HeartbeatAckTimerFail(object o) - { - try - { if (IsConnected) { - this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); - SendText("Heartbeat not received by server, closing connection"); + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); + return; + } + if (IsTryingToConnect) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); + return; + } + try + { + IsTryingToConnect = true; + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); + return; + } + if (Port < 1 || Port > 65535) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); + return; + } + + // clean up previous client + if (_client != null) + { + Disconnect(); + } + DisconnectCalledByUser = false; + + _client = new SecureTCPClient(Hostname, Port, BufferSize); + _client.SocketStatusChange += Client_SocketStatusChange; + if (HeartbeatEnabled) + _client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); + _client.AddressClientConnectedTo = Hostname; + _client.PortNumber = Port; + // SecureClient = c; + + //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); + + ConnectFailTimer = new CTimer(o => + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); + if (IsTryingToConnect) + { + IsTryingToConnect = false; + + //if (ConnectionHasHungCallback != null) + //{ + // ConnectionHasHungCallback(); + //} + //SecureClient.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + } + }, 30000); + + Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); + _client.ConnectToServerAsync(o => + { + Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + } + IsTryingToConnect = false; + + if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(2, this, "_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); + o.ReceiveDataAsync(Receive); + + if (SharedKeyRequired) + { + WaitingForSharedKeyResponse = true; + WaitForSharedKey = new CTimer(timer => + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); + // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); + // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup + o.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + //OnClientReadyForcommunications(false); // Should send false event + }, 15000); + } + else + { + //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key + //required this is called by the shared key being negotiated + if (IsReadyForCommunication == false) + { + OnClientReadyForcommunications(true); // Key not required + } + } + } + else + { + Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); + CheckClosedAndTryReconnect(); + } + }); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "_client connection exception: {0}", ex.Message); + IsTryingToConnect = false; CheckClosedAndTryReconnect(); } - } - catch (Exception ex) + + /// + /// Disconnect method + /// + public void Disconnect() { - this.LogError(ex, "Heartbeat timeout Error on _client: {0}, {1}", Key, ex.Message); - } - } + this.LogVerbose("Disconnect Called"); - /// - /// - /// - void StopWaitForSharedKeyTimer() - { - if (WaitForSharedKey != null) + DisconnectCalledByUser = true; + + // stop trying reconnects, if we are + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + + if (_client != null) + { + DisconnectClient(); + this.LogDebug("Disconnected"); + } + } + + /// + /// DisconnectClient method + /// + public void DisconnectClient() { - WaitForSharedKey.Stop(); - WaitForSharedKey = null; - } - } + if (_client == null) return; - /// - /// General send method - /// - public void SendText(string text) - { - if (!string.IsNullOrEmpty(text)) + Debug.Console(1, this, "Disconnecting client"); + if (IsConnected) + _client.DisconnectFromServer(); + + // close up client. ALWAYS use this when disconnecting. + IsTryingToConnect = false; + + Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : ""); + _client.SocketStatusChange -= Client_SocketStatusChange; + _client.Dispose(); + _client = null; + + if (ConnectFailTimer == null) return; + ConnectFailTimer.Stop(); + ConnectFailTimer.Dispose(); + ConnectFailTimer = null; + } + + #region Methods + + /// + /// Called from Connect failure or Socket Status change if + /// auto reconnect and socket disconnected (Not disconnected by user) + /// + void CheckClosedAndTryReconnect() + { + if (_client != null) + { + Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); + Disconnect(); + } + if (!DisconnectCalledByUser && AutoReconnect) + { + var halfInterval = AutoReconnectIntervalMs / 2; + var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; + Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (AutoReconnectTriggered != null) + AutoReconnectTriggered(this, new EventArgs()); + RetryTimer = new CTimer(o => Connect(), rndTime); + } + } + + /// + /// Receive callback + /// + /// + /// + void Receive(SecureTCPClient client, int numBytes) + { + if (numBytes > 0) + { + string str = string.Empty; + var handler = TextReceivedQueueInvoke; + try + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + Debug.Console(2, this, "_client Received:\r--------\r{0}\r--------", str); + if (!string.IsNullOrEmpty(checkHeartbeat(str))) + { + + if (SharedKeyRequired && str == "SharedKey:") + { + Debug.Console(2, this, "Server asking for shared key, sending"); + SendText(SharedKey + "\n"); + } + else if (SharedKeyRequired && str == "Shared Key Match") + { + StopWaitForSharedKeyTimer(); + + + Debug.Console(2, this, "Shared key confirmed. Ready for communication"); + OnClientReadyForcommunications(true); // Successful key exchange + } + else + { + //var bytesHandler = BytesReceived; + //if (bytesHandler != null) + // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str)); + } + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); + } + if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + client.ReceiveDataAsync(Receive); + + //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. + if (handler != null) + { + var gotLock = DequeueLock.TryEnter(); + if (gotLock) + CrestronInvoke.BeginInvoke((o) => DequeueEvent()); + } + } + else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync + { + client.DisconnectFromServer(); + } + } + + /// + /// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently. + /// It will dequeue items as they are enqueued automatically. + /// + void DequeueEvent() { try { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + while (true) { - _client.SendDataAsync(bytes, bytes.Length, (c, n) => + // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. + var message = MessageQueue.Dequeue(); + var handler = TextReceivedQueueInvoke; + if (handler != null) { - // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? - if (n <= 0) + handler(this, message); + } + } + } + catch (Exception e) + { + this.LogException(e, "DequeueEvent error"); + } + // Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it. + if (DequeueLock != null) + { + DequeueLock.Leave(); + } + } + + void HeartbeatStart() + { + if (HeartbeatEnabled) + { + this.LogVerbose("Starting Heartbeat"); + if (HeartbeatSendTimer == null) + { + + HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); + } + if (HeartbeatAckTimer == null) + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + } + + } + void HeartbeatStop() + { + + if (HeartbeatSendTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Send"); + HeartbeatSendTimer.Stop(); + HeartbeatSendTimer = null; + } + if (HeartbeatAckTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Ack"); + HeartbeatAckTimer.Stop(); + HeartbeatAckTimer = null; + } + + } + void SendHeartbeat(object notused) + { + this.SendText(HeartbeatString); + Debug.Console(2, this, "Sending Heartbeat"); + + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(string received) + { + try + { + if (HeartbeatEnabled) + { + if (!string.IsNullOrEmpty(HeartbeatString)) + { + var remainingText = received.Replace(HeartbeatString, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatString)) { - this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key); + if (HeartbeatAckTimer != null) + { + HeartbeatAckTimer.Reset(HeartbeatInterval * 2); + } + else + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); + return remainingText; } - }); + } } } catch (Exception ex) { - this.LogError(ex, "Error sending text: {1}. Error: {0}", ex.Message, text); + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); } + return received; } - } - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (bytes.Length > 0) + + + void HeartbeatAckTimerFail(object o) { try { - if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - _client.SendData(bytes, bytes.Length); + + if (IsConnected) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); + SendText("Heartbeat not received by server, closing connection"); + CheckClosedAndTryReconnect(); + } + } catch (Exception ex) { - this.LogError(ex, "Error sending bytes. Error: {0}", ex.Message); + ErrorLog.Error("Heartbeat timeout Error on _client: {0}, {1}", Key, ex); } } - } - /// - /// SocketStatusChange Callback - /// - /// - /// - void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) - { - if (ProgramIsStopping) + /// + /// + /// + void StopWaitForSharedKeyTimer() { - ProgramIsStopping = false; - return; - } - try - { - this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); - - OnConnectionChange(); - // The client could be null or disposed by this time... - if (_client == null || _client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + if (WaitForSharedKey != null) { - HeartbeatStop(); - OnClientReadyForcommunications(false); // socket has gone low - CheckClosedAndTryReconnect(); + WaitForSharedKey.Stop(); + WaitForSharedKey = null; } } - catch (Exception ex) + + /// + /// SendText method + /// + public void SendText(string text) { - this.LogError(ex, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + if (!string.IsNullOrEmpty(text)) + { + try + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + _client.SendDataAsync(bytes, bytes.Length, (c, n) => + { + // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? + if (n <= 0) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); + } + }); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); + } + } } + + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + if (bytes.Length > 0) + { + try + { + if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + _client.SendData(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); + } + } + } + + /// + /// SocketStatusChange Callback + /// + /// + /// + void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) + { + if (ProgramIsStopping) + { + ProgramIsStopping = false; + return; + } + try + { + Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); + + OnConnectionChange(); + // The client could be null or disposed by this time... + if (_client == null || _client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + HeartbeatStop(); + OnClientReadyForcommunications(false); // socket has gone low + CheckClosedAndTryReconnect(); + } + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + } + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + var handler = ConnectionChange; + if (handler == null) return; + + handler(this, new GenericSocketStatusChageEventArgs(this)); + } + + /// + /// Helper to fire ClientReadyForCommunications event + /// + void OnClientReadyForcommunications(bool isReady) + { + IsReadyForCommunication = isReady; + if (IsReadyForCommunication) + HeartbeatStart(); + + var handler = ClientReadyForCommunications; + if (handler == null) return; + + handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + } + #endregion } - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - var handler = ConnectionChange; - if (handler == null) return; - - handler(this, new GenericSocketStatusChageEventArgs(this)); - } - - /// - /// Helper to fire ClientReadyForCommunications event - /// - void OnClientReadyForcommunications(bool isReady) - { - IsReadyForCommunication = isReady; - if (IsReadyForCommunication) - HeartbeatStart(); - - var handler = ClientReadyForCommunications; - if (handler == null) return; - - handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); - } - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/src/PepperDash.Core/Comm/GenericSecureTcpIpClient_ForServer.cs index 84af5a47..49350164 100644 --- a/src/PepperDash.Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/src/PepperDash.Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -15,905 +15,895 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; -namespace PepperDash.Core; - -/// -/// Generic secure TCP/IP client for server -/// -public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect +namespace PepperDash.Core { /// - /// Band aid delegate for choked server + /// Generic secure TCP/IP client for server /// - internal delegate void ConnectionHasHungCallbackDelegate(); - - #region Events - - //public event EventHandler BytesReceived; - - /// - /// Notifies of text received - /// - public event EventHandler TextReceived; - - /// - /// Notifies of auto reconnect sequence triggered - /// - public event EventHandler AutoReconnectTriggered; - - /// - /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. - /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. - /// - public event EventHandler TextReceivedQueueInvoke; - - /// - /// Notifies of socket status change - /// - public event EventHandler ConnectionChange; - - - /// - /// This is something of a band-aid callback. If the client times out during the connection process, because the server - /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help - /// keep a watch on the server and reset it if necessary. - /// - internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; - - /// - /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require - /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. - /// - public event EventHandler ClientReadyForCommunications; - - #endregion - - #region Properties & Variables - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// The port number on which the server is listening. - /// - public int Port { get; set; } - - /// - /// S+ helper - /// - public ushort UPort + public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } + /// + /// Band aid delegate for choked server + /// + internal delegate void ConnectionHasHungCallbackDelegate(); - /// - /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class - /// - public bool SharedKeyRequired { get; set; } + #region Events - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set + //public event EventHandler BytesReceived; + + /// + /// Notifies of text received + /// + public event EventHandler TextReceived; + + /// + /// Notifies of auto reconnect sequence triggered + /// + public event EventHandler AutoReconnectTriggered; + + /// + /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. + /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. + /// + public event EventHandler TextReceivedQueueInvoke; + + /// + /// Notifies of socket status change + /// + public event EventHandler ConnectionChange; + + + /// + /// This is something of a band-aid callback. If the client times out during the connection process, because the server + /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help + /// keep a watch on the server and reset it if necessary. + /// + internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; + + /// + /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require + /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. + /// + public event EventHandler ClientReadyForCommunications; + + #endregion + + #region Properties & Variables + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// S+ helper + /// + public ushort UPort { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } } - } - /// - /// SharedKey is sent for verification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module - /// - public string SharedKey { get; set; } + /// + /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class + /// + public bool SharedKeyRequired { get; set; } - /// - /// flag to show the client is waiting for the server to send the shared key - /// - private bool WaitingForSharedKeyResponse { get; set; } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Semaphore on connect method - /// - bool IsTryingToConnect; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired { - if (Client != null) - return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; - else - return false; - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is ready for communication after shared key exchange - /// - public bool IsReadyForCommunication { get; set; } - - /// - /// S+ helper for IsReadyForCommunication - /// - public ushort UIsReadyForCommunication - { - get { return (ushort)(IsReadyForCommunication ? 1 : 0); } - } - - /// - /// Client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - if (Client != null) - return Client.ClientStatus; - else - return SocketStatus.SOCKET_STATUS_NO_CONNECT; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Flag Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// private Timer for auto reconnect - /// - System.Timers.Timer RetryTimer; - - - /// - /// - /// - public bool HeartbeatEnabled { get; set; } - /// - /// - /// - public ushort UHeartbeatEnabled - { - get { return (ushort)(HeartbeatEnabled ? 1 : 0); } - set { HeartbeatEnabled = value == 1; } - } - - /// - /// - /// - public string HeartbeatString { get; set; } - //public int HeartbeatInterval = 50000; - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatInterval { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } } - - System.Timers.Timer HeartbeatSendTimer; - System.Timers.Timer HeartbeatAckTimer; - /// - /// Used to force disconnection on a dead connect attempt - /// - System.Timers.Timer ConnectFailTimer; - System.Timers.Timer WaitForSharedKey; - private int ConnectionCount; - /// - /// Internal secure client - /// - SecureTCPClient Client; - - bool ProgramIsStopping; - - /// - /// Queue lock - /// - private readonly object _dequeueLock = new(); - - /// - /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before - /// calling initialize. - /// - public int ReceiveQueueSize { get; set; } - - /// - /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before - /// calling initialize. - /// - private CrestronQueue MessageQueue; - - - #endregion - - #region Constructors - - /// - /// Constructor - /// - /// - /// - /// - /// - public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize) - : base(key) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Hostname = address; - Port = port; - BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; - - } - - /// - /// Constructor for S+ - /// - public GenericSecureTcpIpClient_ForServer() - : base("Uninitialized Secure Tcp Client For Server") - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - } - - /// - /// Contstructor that sets all properties by calling the initialize method with a config object. - /// - /// - /// - public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject) - : base(key) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Initialize(clientConfigObject); - } - - #endregion - - #region Methods - - /// - /// Initializes the client's key property, which is used to identify this client instance. - /// - /// The unique key that identifies this client instance. - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client. - /// - /// The configuration object containing the client's settings. - public void Initialize(TcpClientConfigObject clientConfigObject) - { - try - { - if (clientConfigObject != null) + set { - var TcpSshProperties = clientConfigObject.Control.TcpSshProperties; - Hostname = TcpSshProperties.Address; - AutoReconnect = TcpSshProperties.AutoReconnect; - AutoReconnectIntervalMs = TcpSshProperties.AutoReconnectIntervalMs > 1000 ? - TcpSshProperties.AutoReconnectIntervalMs : 5000; - SharedKey = clientConfigObject.SharedKey; - SharedKeyRequired = clientConfigObject.SharedKeyRequired; - HeartbeatEnabled = clientConfigObject.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ? - clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15; - HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch; - Port = TcpSshProperties.Port; - BufferSize = TcpSshProperties.BufferSize > 2000 ? TcpSshProperties.BufferSize : 2000; - ReceiveQueueSize = clientConfigObject.ReceiveQueueSize > 20 ? clientConfigObject.ReceiveQueueSize : 20; - MessageQueue = new CrestronQueue(ReceiveQueueSize); + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; } - else + } + + /// + /// Gets or sets the SharedKey + /// + public string SharedKey { get; set; } + + /// + /// flag to show the client is waiting for the server to send the shared key + /// + private bool WaitingForSharedKeyResponse { get; set; } + + /// + /// Gets or sets the BufferSize + /// + public int BufferSize { get; set; } + + /// + /// Semaphore on connect method + /// + bool IsTryingToConnect; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (Client != null) + return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; + else + return false; + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is ready for communication after shared key exchange + /// + public bool IsReadyForCommunication { get; set; } + + /// + /// S+ helper for IsReadyForCommunication + /// + public ushort UIsReadyForCommunication + { + get { return (ushort)(IsReadyForCommunication ? 1 : 0); } + } + + /// + /// Client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + if (Client != null) + return Client.ClientStatus; + else + return SocketStatus.SOCKET_STATUS_NO_CONNECT; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// bool to track if auto reconnect should be set on the socket + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Flag Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// private Timer for auto reconnect + /// + CTimer RetryTimer; + + + /// + /// + /// + public bool HeartbeatEnabled { get; set; } + /// + /// + /// + public ushort UHeartbeatEnabled + { + get { return (ushort)(HeartbeatEnabled ? 1 : 0); } + set { HeartbeatEnabled = value == 1; } + } + + /// + /// + /// + public string HeartbeatString { get; set; } + //public int HeartbeatInterval = 50000; + + /// + /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ + /// + public int HeartbeatInterval { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } } + + CTimer HeartbeatSendTimer; + CTimer HeartbeatAckTimer; + /// + /// Used to force disconnection on a dead connect attempt + /// + CTimer ConnectFailTimer; + CTimer WaitForSharedKey; + private int ConnectionCount; + /// + /// Internal secure client + /// + SecureTCPClient Client; + + bool ProgramIsStopping; + + /// + /// Queue lock + /// + CCriticalSection DequeueLock = new CCriticalSection(); + + /// + /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before + /// calling initialize. + /// + public int ReceiveQueueSize { get; set; } + + /// + /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before + /// calling initialize. + /// + private CrestronQueue MessageQueue; + + + #endregion + + #region Constructors + + /// + /// Constructor + /// + /// + /// + /// + /// + public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize) + : base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Hostname = address; + Port = port; + BufferSize = bufferSize; + AutoReconnectIntervalMs = 5000; + + } + + /// + /// Constructor for S+ + /// + public GenericSecureTcpIpClient_ForServer() + : base("Uninitialized Secure Tcp Client For Server") + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + } + + /// + /// Contstructor that sets all properties by calling the initialize method with a config object. + /// + /// + /// + public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject) + : base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Initialize(clientConfigObject); + } + + #endregion + + #region Methods + + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client. + /// + /// + public void Initialize(TcpClientConfigObject clientConfigObject) + { + try + { + if (clientConfigObject != null) + { + var TcpSshProperties = clientConfigObject.Control.TcpSshProperties; + Hostname = TcpSshProperties.Address; + AutoReconnect = TcpSshProperties.AutoReconnect; + AutoReconnectIntervalMs = TcpSshProperties.AutoReconnectIntervalMs > 1000 ? + TcpSshProperties.AutoReconnectIntervalMs : 5000; + SharedKey = clientConfigObject.SharedKey; + SharedKeyRequired = clientConfigObject.SharedKeyRequired; + HeartbeatEnabled = clientConfigObject.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ? + clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15; + HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch; + Port = TcpSshProperties.Port; + BufferSize = TcpSshProperties.BufferSize > 2000 ? TcpSshProperties.BufferSize : 2000; + ReceiveQueueSize = clientConfigObject.ReceiveQueueSize > 20 ? clientConfigObject.ReceiveQueueSize : 20; + MessageQueue = new CrestronQueue(ReceiveQueueSize); + } + else + { + ErrorLog.Error("Could not initialize client with key: {0}", Key); + } + } + catch { ErrorLog.Error("Could not initialize client with key: {0}", Key); } } - catch - { - ErrorLog.Error("Could not initialize client with key: {0}", Key); - } - } - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { - this.LogInformation("Program stopping. Closing Client connection"); - ProgramIsStopping = true; - Disconnect(); - } - - } - - /// - /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. - /// - public void Connect() - { - ConnectionCount++; - this.LogVerbose("Attempting connect Count:{0}", ConnectionCount); - - - if (IsConnected) - { - this.LogInformation("Already connected. Ignoring."); - return; - } - if (IsTryingToConnect) - { - this.LogInformation("Already trying to connect. Ignoring."); - return; - } - try - { - IsTryingToConnect = true; - if (RetryTimer != null) + if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) { - RetryTimer.Stop(); - RetryTimer = null; - } - if (string.IsNullOrEmpty(Hostname)) - { - this.LogWarning("DynamicTcpClient: No address set"); - return; - } - if (Port < 1 || Port > 65535) - { - this.LogWarning("DynamicTcpClient: Invalid port"); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - this.LogWarning("DynamicTcpClient: No Shared Key set"); - return; + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); + ProgramIsStopping = true; + Disconnect(); } - // clean up previous client - if (Client != null) - { - Cleanup(); - } - DisconnectCalledByUser = false; - - Client = new SecureTCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange += Client_SocketStatusChange; - if (HeartbeatEnabled) - Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); - Client.AddressClientConnectedTo = Hostname; - Client.PortNumber = Port; - // SecureClient = c; - - //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); - - ConnectFailTimer = new System.Timers.Timer(30000) { AutoReset = false }; - ConnectFailTimer.Elapsed += (s, e) => - { - this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); - if (IsTryingToConnect) - { - IsTryingToConnect = false; - - //if (ConnectionHasHungCallback != null) - //{ - // ConnectionHasHungCallback(); - //} - //SecureClient.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - } - }; - ConnectFailTimer.Start(); - - this.LogVerbose("Making Connection Count:{0}", ConnectionCount); - Client.ConnectToServerAsync(o => - { - this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount); - - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - } - IsTryingToConnect = false; - - if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); - o.ReceiveDataAsync(Receive); - - if (SharedKeyRequired) - { - WaitingForSharedKeyResponse = true; - WaitForSharedKey = new System.Timers.Timer(15000) { AutoReset = false }; - WaitForSharedKey.Elapsed += (s, e) => - { - this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); - // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup - o.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - //OnClientReadyForcommunications(false); // Should send false event - }; - WaitForSharedKey.Start(); - } - else - { - //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key - //required this is called by the shared key being negotiated - if (IsReadyForCommunication == false) - { - OnClientReadyForcommunications(true); // Key not required - } - } - } - else - { - this.LogWarning("Connect attempt failed {0}", o.ClientStatus); - CheckClosedAndTryReconnect(); - } - }); - } - catch (Exception ex) - { - this.LogError("Client connection exception: {0}", ex.Message); - IsTryingToConnect = false; - CheckClosedAndTryReconnect(); - } - } - - /// - /// - /// - public void Disconnect() - { - this.LogVerbose("Disconnect Called"); - - DisconnectCalledByUser = true; - if (IsConnected) - { - Client.DisconnectFromServer(); - - } - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - Cleanup(); - } - - /// - /// Internal call to close up client. ALWAYS use this when disconnecting. - /// - void Cleanup() - { - IsTryingToConnect = false; - - if (Client != null) - { - //SecureClient.DisconnectFromServer(); - this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.Dispose(); - Client = null; - } - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - ConnectFailTimer.Dispose(); - ConnectFailTimer = null; - } - } - - - /// ff - /// Called from Connect failure or Socket Status change if - /// auto reconnect and socket disconnected (Not disconnected by user) - /// - void CheckClosedAndTryReconnect() - { - if (Client != null) - { - this.LogVerbose("Cleaning up remotely closed/failed connection."); - Cleanup(); - } - if (!DisconnectCalledByUser && AutoReconnect) - { - var halfInterval = AutoReconnectIntervalMs / 2; - var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; - this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime); - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - if (AutoReconnectTriggered != null) - AutoReconnectTriggered(this, new EventArgs()); - RetryTimer = new System.Timers.Timer(rndTime) { AutoReset = false }; - RetryTimer.Elapsed += (s, e) => Connect(); - RetryTimer.Start(); - } - } - - /// - /// Receive callback - /// - /// - /// - void Receive(SecureTCPClient client, int numBytes) - { - if (numBytes > 0) - { - string str = string.Empty; - var handler = TextReceivedQueueInvoke; - try - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - this.LogVerbose("Client Received:\r--------\r{0}\r--------", str); - if (!string.IsNullOrEmpty(checkHeartbeat(str))) - { - - if (SharedKeyRequired && str == "SharedKey:") - { - this.LogVerbose("Server asking for shared key, sending"); - SendText(SharedKey + "\n"); - } - else if (SharedKeyRequired && str == "Shared Key Match") - { - StopWaitForSharedKeyTimer(); - - - this.LogVerbose("Shared key confirmed. Ready for communication"); - OnClientReadyForcommunications(true); // Successful key exchange - } - else - { - //var bytesHandler = BytesReceived; - //if (bytesHandler != null) - // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); - if (handler != null) - { - MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str)); - } - } - } - } - catch (Exception ex) - { - this.LogError("Error receiving data: {1}. Error: {0}", ex.Message, str); - } - if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - client.ReceiveDataAsync(Receive); - - //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. - if (handler != null) - { - if (Monitor.TryEnter(_dequeueLock)) - Task.Run(() => DequeueEvent()); - } - } - else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync - { - client.DisconnectFromServer(); - } - } - - /// - /// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently. - /// It will dequeue items as they are enqueued automatically. - /// - void DequeueEvent() - { - try - { - while (true) - { - // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. - var message = MessageQueue.Dequeue(); - var handler = TextReceivedQueueInvoke; - if (handler != null) - { - handler(this, message); - } - } - } - catch (Exception e) - { - this.LogError("DequeueEvent error: {0}", e.Message, e); - } - // Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it. - Monitor.Exit(_dequeueLock); - } - - void HeartbeatStart() - { - if (HeartbeatEnabled) - { - this.LogVerbose("Starting Heartbeat"); - if (HeartbeatSendTimer == null) - { - - HeartbeatSendTimer = new System.Timers.Timer(HeartbeatInterval) { AutoReset = true }; - HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null); - HeartbeatSendTimer.Start(); - } - if (HeartbeatAckTimer == null) - { - HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); - } } - } - void HeartbeatStop() - { - - if (HeartbeatSendTimer != null) + /// + /// Connect method + /// + public void Connect() { - this.LogVerbose("Stopping Heartbeat Send"); - HeartbeatSendTimer.Stop(); - HeartbeatSendTimer = null; - } - if (HeartbeatAckTimer != null) - { - this.LogVerbose("Stopping Heartbeat Ack"); - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer = null; - } + ConnectionCount++; + Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); - } - void SendHeartbeat(object notused) - { - this.SendText(HeartbeatString); - this.LogVerbose("Sending Heartbeat"); - - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(string received) - { - try - { - if (HeartbeatEnabled) - { - if (!string.IsNullOrEmpty(HeartbeatString)) - { - var remainingText = received.Replace(HeartbeatString, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatString)) - { - if (HeartbeatAckTimer != null) - { - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer.Interval = HeartbeatInterval * 2; - HeartbeatAckTimer.Start(); - } - else - { - HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); - } - this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString); - return remainingText; - } - } - } - } - catch (Exception ex) - { - this.LogError("Error checking heartbeat: {0}", ex.Message, ex); - } - return received; - } - - - - void HeartbeatAckTimerFail(object o) - { - try - { if (IsConnected) { - this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); - SendText("Heartbeat not received by server, closing connection"); + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); + return; + } + if (IsTryingToConnect) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); + return; + } + try + { + IsTryingToConnect = true; + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); + return; + } + if (Port < 1 || Port > 65535) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); + return; + } + + // clean up previous client + if (Client != null) + { + Cleanup(); + } + DisconnectCalledByUser = false; + + Client = new SecureTCPClient(Hostname, Port, BufferSize); + Client.SocketStatusChange += Client_SocketStatusChange; + if (HeartbeatEnabled) + Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); + Client.AddressClientConnectedTo = Hostname; + Client.PortNumber = Port; + // SecureClient = c; + + //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); + + ConnectFailTimer = new CTimer(o => + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); + if (IsTryingToConnect) + { + IsTryingToConnect = false; + + //if (ConnectionHasHungCallback != null) + //{ + // ConnectionHasHungCallback(); + //} + //SecureClient.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + } + }, 30000); + + Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); + Client.ConnectToServerAsync(o => + { + Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + } + IsTryingToConnect = false; + + if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); + o.ReceiveDataAsync(Receive); + + if (SharedKeyRequired) + { + WaitingForSharedKeyResponse = true; + WaitForSharedKey = new CTimer(timer => + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); + // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); + // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup + o.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + //OnClientReadyForcommunications(false); // Should send false event + }, 15000); + } + else + { + //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key + //required this is called by the shared key being negotiated + if (IsReadyForCommunication == false) + { + OnClientReadyForcommunications(true); // Key not required + } + } + } + else + { + Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); + CheckClosedAndTryReconnect(); + } + }); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); + IsTryingToConnect = false; CheckClosedAndTryReconnect(); } - } - catch (Exception ex) + + /// + /// Disconnect method + /// + public void Disconnect() { - this.LogError("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); - } - } + this.LogVerbose("Disconnect Called"); - /// - /// - /// - void StopWaitForSharedKeyTimer() - { - if (WaitForSharedKey != null) + DisconnectCalledByUser = true; + if (IsConnected) + { + Client.DisconnectFromServer(); + + } + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + Cleanup(); + } + + /// + /// Internal call to close up client. ALWAYS use this when disconnecting. + /// + void Cleanup() { - WaitForSharedKey.Stop(); - WaitForSharedKey = null; - } - } + IsTryingToConnect = false; - /// - /// General send method - /// - public void SendText(string text) - { - if (!string.IsNullOrEmpty(text)) + if (Client != null) + { + //SecureClient.DisconnectFromServer(); + Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); + Client.SocketStatusChange -= Client_SocketStatusChange; + Client.Dispose(); + Client = null; + } + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + ConnectFailTimer.Dispose(); + ConnectFailTimer = null; + } + } + + + /// ff + /// Called from Connect failure or Socket Status change if + /// auto reconnect and socket disconnected (Not disconnected by user) + /// + void CheckClosedAndTryReconnect() + { + if (Client != null) + { + Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); + Cleanup(); + } + if (!DisconnectCalledByUser && AutoReconnect) + { + var halfInterval = AutoReconnectIntervalMs / 2; + var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; + Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if(AutoReconnectTriggered != null) + AutoReconnectTriggered(this, new EventArgs()); + RetryTimer = new CTimer(o => Connect(), rndTime); + } + } + + /// + /// Receive callback + /// + /// + /// + void Receive(SecureTCPClient client, int numBytes) + { + if (numBytes > 0) + { + string str = string.Empty; + var handler = TextReceivedQueueInvoke; + try + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); + if (!string.IsNullOrEmpty(checkHeartbeat(str))) + { + + if (SharedKeyRequired && str == "SharedKey:") + { + Debug.Console(2, this, "Server asking for shared key, sending"); + SendText(SharedKey + "\n"); + } + else if (SharedKeyRequired && str == "Shared Key Match") + { + StopWaitForSharedKeyTimer(); + + + Debug.Console(2, this, "Shared key confirmed. Ready for communication"); + OnClientReadyForcommunications(true); // Successful key exchange + } + else + { + //var bytesHandler = BytesReceived; + //if (bytesHandler != null) + // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str)); + } + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); + } + if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + client.ReceiveDataAsync(Receive); + + //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. + if (handler != null) + { + var gotLock = DequeueLock.TryEnter(); + if (gotLock) + CrestronInvoke.BeginInvoke((o) => DequeueEvent()); + } + } + else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync + { + client.DisconnectFromServer(); + } + } + + /// + /// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently. + /// It will dequeue items as they are enqueued automatically. + /// + void DequeueEvent() { try { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + while (true) { - Client.SendDataAsync(bytes, bytes.Length, (c, n) => + // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. + var message = MessageQueue.Dequeue(); + var handler = TextReceivedQueueInvoke; + if (handler != null) { - // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? - if (n <= 0) + handler(this, message); + } + } + } + catch (Exception e) + { + this.LogException(e, "DequeueEvent error"); + } + // Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it. + if (DequeueLock != null) + { + DequeueLock.Leave(); + } + } + + void HeartbeatStart() + { + if (HeartbeatEnabled) + { + Debug.Console(2, this, "Starting Heartbeat"); + if (HeartbeatSendTimer == null) + { + + HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); + } + if (HeartbeatAckTimer == null) + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + } + + } + void HeartbeatStop() + { + + if (HeartbeatSendTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Send"); + HeartbeatSendTimer.Stop(); + HeartbeatSendTimer = null; + } + if (HeartbeatAckTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Ack"); + HeartbeatAckTimer.Stop(); + HeartbeatAckTimer = null; + } + + } + void SendHeartbeat(object notused) + { + this.SendText(HeartbeatString); + Debug.Console(2, this, "Sending Heartbeat"); + + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(string received) + { + try + { + if (HeartbeatEnabled) + { + if (!string.IsNullOrEmpty(HeartbeatString)) + { + var remainingText = received.Replace(HeartbeatString, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatString)) { - this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key); + if (HeartbeatAckTimer != null) + { + HeartbeatAckTimer.Reset(HeartbeatInterval * 2); + } + else + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); + return remainingText; } - }); + } } } catch (Exception ex) { - this.LogError("Error sending text: {1}. Error: {0}", text, ex.Message); + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); } + return received; } - } - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (bytes.Length > 0) + + + void HeartbeatAckTimerFail(object o) { try { - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - Client.SendData(bytes, bytes.Length); + + if (IsConnected) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); + SendText("Heartbeat not received by server, closing connection"); + CheckClosedAndTryReconnect(); + } + } catch (Exception ex) { - this.LogError("Error sending bytes. Error: {0}", ex.Message, ex); + ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); } } - } - /// - /// SocketStatusChange Callback - /// - /// - /// - void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) - { - if (ProgramIsStopping) + /// + /// + /// + void StopWaitForSharedKeyTimer() { - ProgramIsStopping = false; - return; - } - try - { - this.LogDebug("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); - - OnConnectionChange(); - // The client could be null or disposed by this time... - if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + if (WaitForSharedKey != null) { - HeartbeatStop(); - OnClientReadyForcommunications(false); // socket has gone low - CheckClosedAndTryReconnect(); + WaitForSharedKey.Stop(); + WaitForSharedKey = null; } } - catch (Exception ex) + + /// + /// SendText method + /// + public void SendText(string text) { - this.LogError("Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + if (!string.IsNullOrEmpty(text)) + { + try + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Client.SendDataAsync(bytes, bytes.Length, (c, n) => + { + // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? + if (n <= 0) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); + } + }); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); + } + } } + + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + if (bytes.Length > 0) + { + try + { + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + Client.SendData(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); + } + } + } + + /// + /// SocketStatusChange Callback + /// + /// + /// + void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) + { + if (ProgramIsStopping) + { + ProgramIsStopping = false; + return; + } + try + { + Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); + + OnConnectionChange(); + // The client could be null or disposed by this time... + if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + HeartbeatStop(); + OnClientReadyForcommunications(false); // socket has gone low + CheckClosedAndTryReconnect(); + } + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + } + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); + } + + /// + /// Helper to fire ClientReadyForCommunications event + /// + void OnClientReadyForcommunications(bool isReady) + { + IsReadyForCommunication = isReady; + if (this.IsReadyForCommunication) { HeartbeatStart(); } + var handler = ClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + } + #endregion } - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); - } - - /// - /// Helper to fire ClientReadyForCommunications event - /// - void OnClientReadyForcommunications(bool isReady) - { - IsReadyForCommunication = isReady; - if (this.IsReadyForCommunication) { HeartbeatStart(); } - var handler = ClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); - } - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericSecureTcpIpServer.cs b/src/PepperDash.Core/Comm/GenericSecureTcpIpServer.cs index fafafbdc..6502d544 100644 --- a/src/PepperDash.Core/Comm/GenericSecureTcpIpServer.cs +++ b/src/PepperDash.Core/Comm/GenericSecureTcpIpServer.cs @@ -1,415 +1,421 @@ -using System; +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ +using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; -namespace PepperDash.Core; - -/// -/// Generic secure TCP/IP server -/// -public class GenericSecureTcpIpServer : Device +namespace PepperDash.Core { - #region Events /// - /// Event for Receiving text + /// Generic secure TCP/IP server /// - public event EventHandler TextReceived; - - /// - /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. - /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. - /// - public event EventHandler TextReceivedQueueInvoke; - - /// - /// Event for client connection socket status change - /// - public event EventHandler ClientConnectionChange; - - /// - /// Event for Server State Change - /// - public event EventHandler ServerStateChange; - - /// - /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire - /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. - /// - public event EventHandler ServerClientReadyForCommunications; - - /// - /// A band aid event to notify user that the server has choked. - /// - public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } - - /// - /// - /// - public delegate void ServerHasChokedCallbackDelegate(); - - #endregion - - #region Properties/Variables - - /// - /// Server listen lock - /// - private readonly object _serverLock = new(); - - /// - /// Queue lock - /// - private readonly object _dequeueLock = new(); - - /// - /// Broadcast lock - /// - private readonly object _broadcastLock = new(); - - /// - /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before - /// calling initialize. - /// - public int ReceiveQueueSize { get; set; } - - /// - /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before - /// calling initialize. - /// - private CrestronQueue MessageQueue; - - /// - /// A bandaid client that monitors whether the server is reachable - /// - GenericSecureTcpIpClient_ForServer MonitorClient; - - /// - /// Timer to operate the bandaid monitor client in a loop. - /// - Timer MonitorClientTimer; - - /// - /// - /// - int MonitorClientFailureCount; - - /// - /// 3 by default - /// - public int MonitorClientMaxFailureCount { get; set; } - - /// - /// Text representation of the Socket Status enum values for the server - /// - public string Status + public class GenericSecureTcpIpServer : Device { - get + #region Events + /// + /// Event for Receiving text + /// + public event EventHandler TextReceived; + + /// + /// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread. + /// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event. + /// + public event EventHandler TextReceivedQueueInvoke; + + /// + /// Event for client connection socket status change + /// + public event EventHandler ClientConnectionChange; + + /// + /// Event for Server State Change + /// + public event EventHandler ServerStateChange; + + /// + /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire + /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. + /// + public event EventHandler ServerClientReadyForCommunications; + + /// + /// A band aid event to notify user that the server has choked. + /// + public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } + + /// + /// Delegate for ServerHasChokedCallbackDelegate + /// + public delegate void ServerHasChokedCallbackDelegate(); + + #endregion + + #region Properties/Variables + + /// + /// Server listen lock + /// + CCriticalSection ServerCCSection = new CCriticalSection(); + + /// + /// Queue lock + /// + CCriticalSection DequeueLock = new CCriticalSection(); + + /// + /// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before + /// calling initialize. + /// + public int ReceiveQueueSize { get; set; } + + /// + /// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before + /// calling initialize. + /// + private CrestronQueue MessageQueue; + + /// + /// A bandaid client that monitors whether the server is reachable + /// + GenericSecureTcpIpClient_ForServer MonitorClient; + + /// + /// Timer to operate the bandaid monitor client in a loop. + /// + CTimer MonitorClientTimer; + + /// + /// + /// + int MonitorClientFailureCount; + + /// + /// Gets or sets the MonitorClientMaxFailureCount + /// + public int MonitorClientMaxFailureCount { get; set; } + + /// + /// Text representation of the Socket Status enum values for the server + /// + public string Status { - if (SecureServer != null) - return SecureServer.State.ToString(); - return ServerState.SERVER_NOT_LISTENING.ToString(); - - } - - } - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (SecureServer != null) - return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; - return false; - - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : - // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsListening - { - get - { - if (SecureServer != null) - return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; - else - return false; - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : - // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsListening - { - get { return (ushort)(IsListening ? 1 : 0); } - } - /// - /// Max number of clients this server will allow for connection. Crestron max is 64. This number should be less than 65 - /// - public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable - /// - /// Number of clients currently connected. - /// - public ushort NumberOfClientsConnected - { - get - { - if (SecureServer != null) - return (ushort)SecureServer.NumberOfClientsConnected; - return 0; - } - } - - /// - /// Port Server should listen on - /// - public int Port { get; set; } - - /// - /// S+ helper for Port - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. - /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called - /// - public string SharedKey { get; set; } - - /// - /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received - /// - public bool HeartbeatRequired { get; set; } - - /// - /// S+ Helper for Heartbeat Required - /// - public ushort UHeartbeatRequired - { - set - { - if (value == 1) - HeartbeatRequired = true; - else - HeartbeatRequired = false; - } - } - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatRequiredIntervalMs { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } - - /// - /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer - /// - public string HeartbeatStringToMatch { get; set; } - - //private timers for Heartbeats per client - Dictionary HeartbeatTimerDictionary = new Dictionary(); - - //flags to show the secure server is waiting for client at index to send the shared key - List WaitingForSharedKey = new List(); - - List ClientReadyAfterKeyExchange = new List(); - - /// - /// The connected client indexes - /// - public List ConnectedClientsIndexes = new List(); - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Private flag to note that the server has stopped intentionally - /// - private bool ServerStopped { get; set; } - - //Servers - SecureTCPServer SecureServer; - - /// - /// - /// - bool ProgramIsStopping; - - #endregion - - #region Constructors - /// - /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. - /// - public GenericSecureTcpIpServer() - : base("Uninitialized Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - } - - /// - /// constructor with debug key set at instantiation. Make sure to set all properties before listening. - /// - /// - public GenericSecureTcpIpServer(string key) - : base("Uninitialized Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Key = key; - } - - /// - /// Contstructor that sets all properties by calling the initialize method with a config object. This does set Queue size. - /// - /// - public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject) - : base("Uninitialized Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Initialize(serverConfigObject); - } - #endregion - - #region Methods - Server Actions - /// - /// Disconnects all clients and stops the server - /// - public void KillServer() - { - ServerStopped = true; - if (MonitorClient != null) - { - MonitorClient.Disconnect(); - } - DisconnectAllClientsForShutdown(); - StopListening(); - } - - /// - /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ - /// - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialze the server - /// - /// - public void Initialize(TcpServerConfigObject serverConfigObject) - { - try - { - if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) + get { - Key = serverConfigObject.Key; - MaxClients = serverConfigObject.MaxClients; - Port = serverConfigObject.Port; - SharedKeyRequired = serverConfigObject.SharedKeyRequired; - SharedKey = serverConfigObject.SharedKey; - HeartbeatRequired = serverConfigObject.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; - HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; - BufferSize = serverConfigObject.BufferSize; - ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20; - MessageQueue = new CrestronQueue(ReceiveQueueSize); + if (SecureServer != null) + return SecureServer.State.ToString(); + return ServerState.SERVER_NOT_LISTENING.ToString(); + } - else + + } + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (SecureServer != null) + return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; + return false; + + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : + // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is connected + /// + public bool IsListening + { + get + { + if (SecureServer != null) + return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; + else + return false; + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : + // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsListening + { + get { return (ushort)(IsListening ? 1 : 0); } + } + /// + /// Max number of clients this server will allow for connection. Crestron max is 64. This number should be less than 65 + /// + public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable + /// + /// Number of clients currently connected. + /// + public ushort NumberOfClientsConnected + { + get + { + if (SecureServer != null) + return (ushort)SecureServer.NumberOfClientsConnected; + return 0; + } + } + + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// S+ helper for Port + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// Gets or sets the SharedKey + /// + public string SharedKey { get; set; } + + /// + /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received + /// + public bool HeartbeatRequired { get; set; } + + /// + /// S+ Helper for Heartbeat Required + /// + public ushort UHeartbeatRequired + { + set + { + if (value == 1) + HeartbeatRequired = true; + else + HeartbeatRequired = false; + } + } + + /// + /// Gets or sets the HeartbeatRequiredIntervalMs + /// + public int HeartbeatRequiredIntervalMs { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } + + /// + /// Gets or sets the HeartbeatStringToMatch + /// + public string HeartbeatStringToMatch { get; set; } + + //private timers for Heartbeats per client + Dictionary HeartbeatTimerDictionary = new Dictionary(); + + //flags to show the secure server is waiting for client at index to send the shared key + List WaitingForSharedKey = new List(); + + List ClientReadyAfterKeyExchange = new List(); + + /// + /// The connected client indexes + /// + public List ConnectedClientsIndexes = new List(); + + /// + /// Gets or sets the BufferSize + /// + public int BufferSize { get; set; } + + /// + /// Private flag to note that the server has stopped intentionally + /// + private bool ServerStopped { get; set; } + + //Servers + SecureTCPServer SecureServer; + + /// + /// + /// + bool ProgramIsStopping; + + #endregion + + #region Constructors + /// + /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. + /// + public GenericSecureTcpIpServer() + : base("Uninitialized Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + } + + /// + /// constructor with debug key set at instantiation. Make sure to set all properties before listening. + /// + /// + public GenericSecureTcpIpServer(string key) + : base("Uninitialized Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Key = key; + } + + /// + /// Contstructor that sets all properties by calling the initialize method with a config object. This does set Queue size. + /// + /// + public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject) + : base("Uninitialized Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Initialize(serverConfigObject); + } + #endregion + + #region Methods - Server Actions + /// + /// KillServer method + /// + public void KillServer() + { + ServerStopped = true; + if (MonitorClient != null) + { + MonitorClient.Disconnect(); + } + DisconnectAllClientsForShutdown(); + StopListening(); + } + + /// + /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ + /// + /// + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Initialze the server + /// + /// + public void Initialize(TcpServerConfigObject serverConfigObject) + { + try + { + if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) + { + Key = serverConfigObject.Key; + MaxClients = serverConfigObject.MaxClients; + Port = serverConfigObject.Port; + SharedKeyRequired = serverConfigObject.SharedKeyRequired; + SharedKey = serverConfigObject.SharedKey; + HeartbeatRequired = serverConfigObject.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; + HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; + BufferSize = serverConfigObject.BufferSize; + ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20; + MessageQueue = new CrestronQueue(ReceiveQueueSize); + } + else + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + catch { ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); } } - catch - { - ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - /// - /// Start listening on the specified port - /// - public void Listen() - { - lock (_serverLock) + /// + /// Listen method + /// + public void Listen() { - try - { - if (Port < 1 || Port > 65535) + ServerCCSection.Enter(); + try { - this.LogError("Server '{0}': Invalid port", Key); - ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - this.LogError("Server '{0}': No Shared Key set", Key); - ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); - return; - } + if (Port < 1 || Port > 65535) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); + ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); + ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); + return; + } if (SecureServer == null) @@ -430,379 +436,381 @@ public class GenericSecureTcpIpServer : Device SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) { - this.LogError("Error starting WaitForConnectionAsync {0}", status); + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status); } else { ServerStopped = false; } OnServerStateChange(SecureServer.State); - this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus); + ServerCCSection.Leave(); + } + catch (Exception ex) + { + ServerCCSection.Leave(); + ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); + } } - catch (Exception ex) - { - this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key); - } - } // end lock - } - /// - /// Stop Listeneing - /// - public void StopListening() - { + /// + /// StopListening method + /// + public void StopListening() + { try { - this.LogVerbose("Stopping Listener"); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); if (SecureServer != null) { SecureServer.Stop(); - this.LogVerbose("Server State: {0}", SecureServer.State); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State); OnServerStateChange(SecureServer.State); } ServerStopped = true; } catch (Exception ex) { - this.LogException(ex, "Error stopping server. Error: {0}", ex.Message); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex); } - } + } - /// - /// Disconnects Client - /// - /// - public void DisconnectClient(uint client) - { - try + /// + /// Disconnects Client + /// + /// + /// + /// DisconnectClient method + /// + public void DisconnectClient(uint client) { - SecureServer.Disconnect(client); - this.LogVerbose("Disconnected client index: {0}", client); - } - catch (Exception ex) - { - this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message); - } - } - /// - /// Disconnect All Clients - /// - public void DisconnectAllClientsForShutdown() - { - this.LogInformation("Disconnecting All Clients"); - if (SecureServer != null) - { - SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange; - foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + try { - var i = index; - if (!SecureServer.ClientConnected(index)) - continue; - try - { - SecureServer.Disconnect(i); - this.LogInformation("Disconnected client index: {0}", i); - } - catch (Exception ex) - { - this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message); - } + SecureServer.Disconnect(client); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); } - this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus); + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); + } + } + /// + /// DisconnectAllClientsForShutdown method + /// + public void DisconnectAllClientsForShutdown() + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); + if (SecureServer != null) + { + SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange; + foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + { + var i = index; + if (!SecureServer.ClientConnected(index)) + continue; + try + { + SecureServer.Disconnect(i); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); + } + } + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus); + } + + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); + ConnectedClientsIndexes.Clear(); + + if (!ProgramIsStopping) + { + OnConnectionChange(); + OnServerStateChange(SecureServer.State); //State shows both listening and connected + } + + // var o = new { }; } - this.LogInformation("Disconnected All Clients"); - ConnectedClientsIndexes.Clear(); - - if (!ProgramIsStopping) + /// + /// Broadcast text from server to all connected clients + /// + /// + /// + /// BroadcastText method + /// + public void BroadcastText(string text) { - OnConnectionChange(); - OnServerStateChange(SecureServer.State); //State shows both listening and connected + CCriticalSection CCBroadcast = new CCriticalSection(); + CCBroadcast.Enter(); + try + { + if (ConnectedClientsIndexes.Count > 0) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + foreach (uint i in ConnectedClientsIndexes) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) + { + SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); + if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) + this.LogVerbose("{error}", error); + } + } + } + CCBroadcast.Leave(); + } + catch (Exception ex) + { + CCBroadcast.Leave(); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); + } } - // var o = new { }; - } - - /// - /// Broadcast text from server to all connected clients - /// - /// - public void BroadcastText(string text) - { - lock (_broadcastLock) + /// + /// Not sure this is useful in library, maybe Pro?? + /// + /// + /// + /// + /// SendTextToClient method + /// + public void SendTextToClient(string text, uint clientIndex) { - try - { - if (ConnectedClientsIndexes.Count > 0) + try { byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - foreach (uint i in ConnectedClientsIndexes) + if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) - { - SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); - if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) - this.LogVerbose("{error}", error); - } + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); } } - } - catch (Exception ex) - { - this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message); - } - } // end lock - } - - /// - /// Not sure this is useful in library, maybe Pro?? - /// - /// - /// - public void SendTextToClient(string text, uint clientIndex) - { - try - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + catch (Exception ex) { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); } } - catch (Exception ex) - { - this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); - } - } - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(uint clientIndex, string received) - { - try + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(uint clientIndex, string received) { - if (HeartbeatRequired) + try { - if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) + if (HeartbeatRequired) { - var remainingText = received.Replace(HeartbeatStringToMatch, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatStringToMatch)) + if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + var remainingText = received.Replace(HeartbeatStringToMatch, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatStringToMatch)) { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; - HeartbeatTimerDictionary[clientIndex].Start(); + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); + // Return Heartbeat + SendTextToClient(HeartbeatStringToMatch, clientIndex); + return remainingText; } - else - { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); - } - this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); - // Return Heartbeat - SendTextToClient(HeartbeatStringToMatch, clientIndex); - return remainingText; - } - } - else - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; - HeartbeatTimerDictionary[clientIndex].Start(); } else { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); } - this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex); } } - } - catch (Exception ex) - { - this.LogException(ex, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - /// - /// Get the IP Address for the client at the specifed index - /// - /// - /// - public string GetClientIPAddress(uint clientIndex) - { - this.LogInformation("GetClientIPAddress Index: {0}", clientIndex); - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - { - var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa); - return ipa; - - } - else - { - return ""; - } - } - - #endregion - - #region Methods - HeartbeatTimer Callback - - void HeartbeatTimer_CallbackFunction(object o) - { - uint clientIndex = 99999; - string address = string.Empty; - try - { - clientIndex = (uint)o; - address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - - this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", - address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); - - if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); - - var discoResult = SecureServer.Disconnect(clientIndex); - - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + catch (Exception ex) { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + /// + /// Get the IP Address for the client at the specifed index + /// + /// + /// + /// + /// GetClientIPAddress method + /// + public string GetClientIPAddress(uint clientIndex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + { + var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); + return ipa; + + } + else + { + return ""; } } - catch (Exception ex) - { - ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); - } - } - #endregion + #endregion - #region Methods - Socket Status Changed Callbacks - /// - /// Secure Server Socket Status Changed Callback - /// - /// - /// - /// - void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - try + #region Methods - HeartbeatTimer Callback + + void HeartbeatTimer_CallbackFunction(object o) { - - if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + uint clientIndex = 99999; + string address = string.Empty; + try { - this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber); - - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + clientIndex = (uint)o; + address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", + address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); + + if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); + + var discoResult = SecureServer.Disconnect(clientIndex); + //Debug.Console(1, this, "{0}", discoResult); + + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Dispose(); HeartbeatTimerDictionary.Remove(clientIndex); } - if (ClientReadyAfterKeyExchange.Contains(clientIndex)) - ClientReadyAfterKeyExchange.Remove(clientIndex); + } + catch (Exception ex) + { + ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); + } + } + + #endregion + + #region Methods - Socket Status Changed Callbacks + /// + /// Secure Server Socket Status Changed Callback + /// + /// + /// + /// + void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + try + { + + + // Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber); + + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + if (ClientReadyAfterKeyExchange.Contains(clientIndex)) + ClientReadyAfterKeyExchange.Remove(clientIndex); if (WaitingForSharedKey.Contains(clientIndex)) WaitingForSharedKey.Remove(clientIndex); if (SecureServer.MaxNumberOfClientSupported > SecureServer.NumberOfClientsConnected) { Listen(); } - } - } - catch (Exception ex) - { - this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message); - } - //Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state - //after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag - //is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event. - System.Threading.Tasks.Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex))); - } - - #endregion - - #region Methods Connected Callbacks - /// - /// Secure TCP Client Connected to Secure Server Callback - /// - /// - /// - void SecureConnectCallback(SecureTCPServer server, uint clientIndex) - { - try - { - this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), - clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - if (clientIndex != 0) - { - if (server.ClientConnected(clientIndex)) - { - - if (!ConnectedClientsIndexes.Contains(clientIndex)) - { - ConnectedClientsIndexes.Add(clientIndex); - } - if (SharedKeyRequired) - { - if (!WaitingForSharedKey.Contains(clientIndex)) - { - WaitingForSharedKey.Add(clientIndex); - } - byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); - server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - else - { - OnServerClientReadyForCommunications(clientIndex); - } - if (HeartbeatRequired) - { - if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); - } - } - - server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); } } - else + catch (Exception ex) { - this.LogError("Client attempt faulty."); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); } + //Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state + //after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag + //is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event. + CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null); } - catch (Exception ex) + + #endregion + + #region Methods Connected Callbacks + /// + /// Secure TCP Client Connected to Secure Server Callback + /// + /// + /// + void SecureConnectCallback(SecureTCPServer server, uint clientIndex) { - this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message); - } + try + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), + clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + if (clientIndex != 0) + { + if (server.ClientConnected(clientIndex)) + { + + if (!ConnectedClientsIndexes.Contains(clientIndex)) + { + ConnectedClientsIndexes.Add(clientIndex); + } + if (SharedKeyRequired) + { + if (!WaitingForSharedKey.Contains(clientIndex)) + { + WaitingForSharedKey.Add(clientIndex); + } + byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); + server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + else + { + OnServerClientReadyForCommunications(clientIndex); + } + if (HeartbeatRequired) + { + if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); + } + } + + server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); + } + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); + } + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); + } // Rearm the listner SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) { - this.LogError("Socket status connect callback status {0}", status); + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status); if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS) { // There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact. @@ -812,276 +820,279 @@ public class GenericSecureTcpIpServer : Device } } - #endregion + #endregion - #region Methods - Send/Receive Callbacks - /// - /// Secure Received Data Async Callback - /// - /// - /// - /// - void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) - { - if (numberOfBytesReceived > 0) + #region Methods - Send/Receive Callbacks + /// + /// Secure Received Data Async Callback + /// + /// + /// + /// + void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) { + if (numberOfBytesReceived > 0) + { - string received = "Nothing"; - var handler = TextReceivedQueueInvoke; - try - { - byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex); - received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived); - if (WaitingForSharedKey.Contains(clientIndex)) + string received = "Nothing"; + var handler = TextReceivedQueueInvoke; + try { - received = received.Replace("\r", ""); - received = received.Replace("\n", ""); - if (received != SharedKey) + byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex); + received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived); + if (WaitingForSharedKey.Contains(clientIndex)) { - byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting"); - this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); - mySecureTCPServer.SendData(clientIndex, b, b.Length); - mySecureTCPServer.Disconnect(clientIndex); - - return; - } + received = received.Replace("\r", ""); + received = received.Replace("\n", ""); + if (received != SharedKey) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting"); + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); + mySecureTCPServer.SendData(clientIndex, b, b.Length); + mySecureTCPServer.Disconnect(clientIndex); + + return; + } - WaitingForSharedKey.Remove(clientIndex); - byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); - mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null); - OnServerClientReadyForCommunications(clientIndex); - this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex); - } - else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) - { - onTextReceived(received, clientIndex); - if (handler != null) + WaitingForSharedKey.Remove(clientIndex); + byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); + mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null); + OnServerClientReadyForCommunications(clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex); + } + else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) { - MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); + onTextReceived(received, clientIndex); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); + } } } - } - catch (Exception ex) - { - this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message); - } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex); + } if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); - //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. - if (handler != null) - { - if (System.Threading.Monitor.TryEnter(_dequeueLock)) - System.Threading.Tasks.Task.Run(() => DequeueEvent()); + //Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread. + if (handler != null) + { + var gotLock = DequeueLock.TryEnter(); + if (gotLock) + CrestronInvoke.BeginInvoke((o) => DequeueEvent()); + } } - } else { mySecureTCPServer.Disconnect(clientIndex); + } } - } - /// - /// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently. - /// It will dequeue items as they are enqueued automatically. - /// - void DequeueEvent() - { - try + /// + /// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently. + /// It will dequeue items as they are enqueued automatically. + /// + void DequeueEvent() { - while (true) + try { - // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. - var message = MessageQueue.Dequeue(); - var handler = TextReceivedQueueInvoke; + while (true) + { + // Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather. + var message = MessageQueue.Dequeue(); + var handler = TextReceivedQueueInvoke; + if (handler != null) + { + handler(this, message); + } + } + } + catch (Exception e) + { + this.LogException(e, "DequeueEvent error"); + } + // Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it. + if (DequeueLock != null) + { + DequeueLock.Leave(); + } + } + + #endregion + + #region Methods - EventHelpers/Callbacks + + //Private Helper method to call the Connection Change Event + void onConnectionChange(uint clientIndex, SocketStatus clientStatus) + { + if (clientIndex != 0) //0 is error not valid client change + { + var handler = ClientConnectionChange; if (handler != null) { - handler(this, message); + handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus)); } } } - catch (Exception e) - { - this.LogError(e, "DequeueEvent error"); - } - // Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it. - System.Threading.Monitor.Exit(_dequeueLock); - } - - #endregion - - #region Methods - EventHelpers/Callbacks - - //Private Helper method to call the Connection Change Event - void onConnectionChange(uint clientIndex, SocketStatus clientStatus) - { - if (clientIndex != 0) //0 is error not valid client change + + //Private Helper method to call the Connection Change Event + void OnConnectionChange() { + if (ProgramIsStopping) + { + return; + } var handler = ClientConnectionChange; if (handler != null) { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus)); + handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); } } - } - //Private Helper method to call the Connection Change Event - void OnConnectionChange() - { - if (ProgramIsStopping) + //Private Helper Method to call the Text Received Event + void onTextReceived(string text, uint clientIndex) { - return; + var handler = TextReceived; + if (handler != null) + handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); } - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); - } - } - //Private Helper Method to call the Text Received Event - void onTextReceived(string text, uint clientIndex) - { - var handler = TextReceived; - if (handler != null) - handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); - } - - //Private Helper Method to call the Server State Change Event - void OnServerStateChange(ServerState state) - { - if (ProgramIsStopping) + //Private Helper Method to call the Server State Change Event + void OnServerStateChange(ServerState state) { - return; + if (ProgramIsStopping) + { + return; + } + var handler = ServerStateChange; + if (handler != null) + { + handler(this, new GenericTcpServerStateChangedEventArgs(state)); + } } - var handler = ServerStateChange; - if (handler != null) - { - handler(this, new GenericTcpServerStateChangedEventArgs(state)); - } - } - /// - /// Private Event Handler method to handle the closing of connections when the program stops - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) + /// + /// Private Event Handler method to handle the closing of connections when the program stops + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + ProgramIsStopping = true; + // kill bandaid things + if (MonitorClientTimer != null) + MonitorClientTimer.Stop(); + if (MonitorClient != null) + MonitorClient.Disconnect(); + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); + KillServer(); + } + } + + //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation + void OnServerClientReadyForCommunications(uint clientIndex) + { + ClientReadyAfterKeyExchange.Add(clientIndex); + var handler = ServerClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerSocketStatusChangeEventArgs( + this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex))); + } + #endregion + + #region Monitor Client + /// + /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient + /// + void StartMonitorClient() { - ProgramIsStopping = true; - // kill bandaid things if (MonitorClientTimer != null) + { + return; + } + MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); + } + + /// + /// + /// + void RunMonitorClient() + { + MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); + MonitorClient.SharedKeyRequired = this.SharedKeyRequired; + MonitorClient.SharedKey = this.SharedKey; + MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; + //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; + MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); + + MonitorClient.Connect(); + // From here MonitorCLient either connects or hangs, MonitorClient will call back + + } + + /// + /// + /// + void StopMonitorClient() + { + if (MonitorClient == null) + return; + + MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; + MonitorClient.Disconnect(); + MonitorClient = null; + } + + /// + /// On monitor connect, restart the operation + /// + void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) + { + if (args.IsReady) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); MonitorClientTimer.Stop(); - if (MonitorClient != null) - MonitorClient.Disconnect(); - - this.LogInformation("Program stopping. Closing server"); - KillServer(); + MonitorClientTimer = null; + MonitorClientFailureCount = 0; + CrestronEnvironment.Sleep(2000); + StopMonitorClient(); + StartMonitorClient(); + } } - } - //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation - void OnServerClientReadyForCommunications(uint clientIndex) - { - ClientReadyAfterKeyExchange.Add(clientIndex); - var handler = ServerClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerSocketStatusChangeEventArgs( - this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex))); - } - #endregion - - #region Monitor Client - /// - /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient - /// - void StartMonitorClient() - { - if (MonitorClientTimer != null) + /// + /// If the client hangs, add to counter and maybe fire the choke event + /// + void MonitorClientHasHungCallback() { - return; - } - MonitorClientTimer = new Timer(60000) { AutoReset = false }; - MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient(); - MonitorClientTimer.Start(); - } - - /// - /// - /// - void RunMonitorClient() - { - MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); - MonitorClient.SharedKeyRequired = this.SharedKeyRequired; - MonitorClient.SharedKey = this.SharedKey; - MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; - //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; - MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; - - this.LogInformation("Starting monitor check"); - - MonitorClient.Connect(); - // From here MonitorCLient either connects or hangs, MonitorClient will call back - - } - - /// - /// - /// - void StopMonitorClient() - { - if (MonitorClient == null) - return; - - MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; - MonitorClient.Disconnect(); - MonitorClient = null; - } - - /// - /// On monitor connect, restart the operation - /// - void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) - { - if (args.IsReady) - { - this.LogInformation("Monitor client connection success. Disconnecting in 2s"); + MonitorClientFailureCount++; MonitorClientTimer.Stop(); MonitorClientTimer = null; - MonitorClientFailureCount = 0; - CrestronEnvironment.Sleep(2000); StopMonitorClient(); - StartMonitorClient(); - } - } + if (MonitorClientFailureCount < MonitorClientMaxFailureCount) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}", + MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); + StartMonitorClient(); + } + else + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, + "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", + MonitorClientMaxFailureCount); - /// - /// If the client hangs, add to counter and maybe fire the choke event - /// - void MonitorClientHasHungCallback() - { - MonitorClientFailureCount++; - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - StopMonitorClient(); - if (MonitorClientFailureCount < MonitorClientMaxFailureCount) - { - this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}", - MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); - StartMonitorClient(); - } - else - { - this.LogError( - "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", - MonitorClientMaxFailureCount); - - var handler = ServerHasChoked; - if (handler != null) - handler(); - // Some external thing is in charge here. Expected reset of program + var handler = ServerHasChoked; + if (handler != null) + handler(); + // Some external thing is in charge here. Expected reset of program + } } + #endregion } - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericSshClient.cs b/src/PepperDash.Core/Comm/GenericSshClient.cs index 471d8a2c..546a2a67 100644 --- a/src/PepperDash.Core/Comm/GenericSshClient.cs +++ b/src/PepperDash.Core/Comm/GenericSshClient.cs @@ -1,611 +1,672 @@ using System; using System.Collections.Generic; using System.Text; -using System.Timers; +using System.Threading; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; +using Org.BouncyCastle.Utilities; using PepperDash.Core.Logging; using Renci.SshNet; using Renci.SshNet.Common; -namespace PepperDash.Core; - -/// -/// -/// -public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect +namespace PepperDash.Core { - private const string SPlusKey = "Uninitialized SshClient"; - /// - /// Object to enable stream debugging - /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - /// - /// Event that fires when data is received. Delivers args with byte array - /// - public event EventHandler BytesReceived; - - /// - /// Event that fires when data is received. Delivered as text. - /// - public event EventHandler TextReceived; - - /// - /// Event when the connection status changes. - /// - public event EventHandler ConnectionChange; - - ///// - ///// - ///// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// Username for server - /// - public string Username { get; set; } - - /// - /// And... Password for server. That was worth documenting! - /// - public string Password { get; set; } - - /// - /// True when the server is connected - when status == 2. - /// - public bool IsConnected - { - // returns false if no client or not connected - get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - /// /// SSH Client /// - public SocketStatus ClientStatus + public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { - get { return _ClientStatus; } - private set + private const string SPlusKey = "Uninitialized SshClient"; + + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Event that fires when data is received. Delivers args with byte array + /// + public event EventHandler BytesReceived; + + /// + /// Event that fires when data is received. Delivered as text. + /// + public event EventHandler TextReceived; + + /// + /// Event when the connection status changes. + /// + public event EventHandler ConnectionChange; + + /// + /// Gets or sets the Hostname + /// + public string Hostname { get; set; } + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// Gets or sets the Username + /// + public string Username { get; set; } + + /// + /// Gets or sets the Password + /// + public string Password { get; set; } + + /// + /// True when the server is connected - when status == 2. + /// + public bool IsConnected { - if (_ClientStatus == value) - return; - _ClientStatus = value; - OnConnectionChange(); + // returns false if no client or not connected + get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - } - SocketStatus _ClientStatus; - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected with be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)_ClientStatus; } - } + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } - /// - /// Determines whether client will attempt reconnection on failure. Default is true - /// - public bool AutoReconnect { get; set; } - - /// - /// Will be set and unset by connect and disconnect only - /// - public bool ConnectEnabled { get; private set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - - /// - /// Millisecond value, determines the timeout period in between reconnect attempts. - /// Set to 5000 by default - /// - public int AutoReconnectIntervalMs { get; set; } - - SshClient Client; - - ShellStream TheStream; - - Timer ReconnectTimer; - - private System.Threading.SemaphoreSlim connectLock = new System.Threading.SemaphoreSlim(1); - - private bool DisconnectLogged = false; - - /// - /// When true, turns off echo for the SSH session - /// - public bool DisableEcho { get; set; } - - /// - /// Typical constructor. - /// - public GenericSshClient(string key, string hostname, int port, string username, string password) : - base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Key = key; - Hostname = hostname; - Port = port; - Username = username; - Password = password; - AutoReconnectIntervalMs = 5000; - - ReconnectTimer = new Timer { AutoReset = false, Enabled = false }; - ReconnectTimer.Elapsed += (s, e) => + /// + /// Socket status change event + /// + public SocketStatus ClientStatus + { + get { lock (_stateLock) { return _ClientStatus; } } + private set + { + bool shouldFireEvent = false; + lock (_stateLock) { - if (ConnectEnabled) + if (_ClientStatus != value) + { + _ClientStatus = value; + shouldFireEvent = true; + } + } + // Fire event outside lock to avoid deadlock + if (shouldFireEvent) + OnConnectionChange(); + } + } + + private SocketStatus _ClientStatus; + private bool _ConnectEnabled; + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected with be true when this == 2. + /// + public ushort UStatus + { + get { lock (_stateLock) { return (ushort)_ClientStatus; } } + } + + /// + /// Determines whether client will attempt reconnection on failure. Default is true + /// + public bool AutoReconnect { get; set; } + + /// + /// Will be set and unset by connect and disconnect only + /// + public bool ConnectEnabled + { + get { lock (_stateLock) { return _ConnectEnabled; } } + private set { lock (_stateLock) { _ConnectEnabled = value; } } + } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Gets or sets the AutoReconnectIntervalMs + /// + public int AutoReconnectIntervalMs { get; set; } + + private SshClient client; + + private ShellStream shellStream; + + private readonly Timer reconnectTimer; + + //Lock object to prevent simulatneous connect/disconnect operations + //private CCriticalSection connectLock = new CCriticalSection(); + private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1); + + // Thread-safety lock for state changes + private readonly object _stateLock = new object(); + + private volatile bool _isProgramStopping; + + private bool disconnectLogged = false; + + /// + /// When true, turns off echo for the SSH session + /// + public bool DisableEcho { get; set; } + + /// + /// Typical constructor. + /// + public GenericSshClient(string key, string hostname, int port, string username, string password) : + base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Key = key; + Hostname = hostname; + Port = port; + Username = username; + Password = password; + AutoReconnectIntervalMs = 5000; + + reconnectTimer = new Timer(o => + { + if (ConnectEnabled) // Now thread-safe property access { Connect(); } - }; - } + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } - /// - /// S+ Constructor - Must set all properties before calling Connect - /// - public GenericSshClient() - : base(SPlusKey) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - - ReconnectTimer = new Timer { AutoReset = false, Enabled = false }; - ReconnectTimer.Elapsed += (s, e) => + /// + /// S+ Constructor - Must set all properties before calling Connect + /// + public GenericSshClient() + : base(SPlusKey) { - if (ConnectEnabled) - { - Connect(); - } - }; - } + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - if (Client != null) + reconnectTimer = new Timer(o => { + if (ConnectEnabled) // Now thread-safe property access + { + Connect(); + } + }, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } + + /// + /// Handles closing this up when the program shuts down + /// + private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + _isProgramStopping = true; this.LogDebug("Program stopping. Closing connection"); Disconnect(); } } - } - /// - /// Connect to the server, using the provided properties. - /// - public void Connect() - { - // Don't go unless everything is here - if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535 - || Username == null || Password == null) + /// + /// Connect method + /// + public void Connect() { - this.LogError("Connect failed. Check hostname, port, username and password are set or not null"); - return; - } - - ConnectEnabled = true; - - try - { - connectLock.Wait(); - if (IsConnected) + // Don't go unless everything is here + if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535 + || Username == null || Password == null) { - this.LogDebug("Connection already connected. Exiting Connect"); + this.LogError("Connect failed. Check hostname, port, username and password are set or not null"); + return; } - else + + if (_isProgramStopping) { - this.LogDebug("Attempting connect"); - - // Cancel reconnect if running. - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - } - - // Cleanup the old client if it already exists - if (Client != null) - { - this.LogDebug("Cleaning up disconnected client"); - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); - } - - // This handles both password and keyboard-interactive (like on OS-X, 'nixes) - KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); - kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); - PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); - - this.LogDebug("Creating new SshClient"); - ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - Client = new SshClient(connectionInfo); - Client.ErrorOccurred += Client_ErrorOccurred; - - //Attempt to connect - ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; - try - { - Client.Connect(); - - var modes = new Dictionary(); - - if (DisableEcho) - { - modes.Add(TerminalModes.ECHO, 0); - } - - TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes); - if (TheStream.DataAvailable) - { - // empty the buffer if there is data - string str = TheStream.Read(); - } - TheStream.DataReceived += Stream_DataReceived; - this.LogInformation("Connected"); - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; - DisconnectLogged = false; - } - catch (SshConnectionException e) - { - var ie = e.InnerException; // The details are inside!! - - if (ie is SocketException) - { - this.LogException(ie, "CONNECTION failure: Cannot reach host"); - } - - if (ie is System.Net.Sockets.SocketException socketException) - { - this.LogException(ie, "Connection failure: Cannot reach {host} on {port}", - Hostname, Port); - } - if (ie is SshAuthenticationException) - { - this.LogException(ie, "Authentication failure for username {userName}", Username); - } - else - this.LogException(ie, "Error on connect"); - - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Stop(); - ReconnectTimer.Interval = AutoReconnectIntervalMs; - ReconnectTimer.Start(); - } - } - catch (SshOperationTimeoutException ex) - { - this.LogWarning("Connection attempt timed out: {message}", ex.Message); - - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Stop(); - ReconnectTimer.Interval = AutoReconnectIntervalMs; - ReconnectTimer.Start(); - } - } - catch (System.Net.Sockets.SocketException e) - { - // SSH.NET only wraps socket failures in SshConnectionException once the SSH - // protocol handshake begins - a failure before that (e.g. DNS resolution failure, - // "Name or service not known", or connection refused) surfaces as a raw - // SocketException here instead. This is an expected/routine "device offline" - // condition (same as the SshConnectionException case above), so log it at Warning - // instead of falling through to the generic handler's LogException/Error-level - // stack trace dump. - this.LogWarning("Connection failure: Cannot reach {host} on {port} ({message})", Hostname, Port, e.Message); - - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Stop(); - ReconnectTimer.Interval = AutoReconnectIntervalMs; - ReconnectTimer.Start(); - } - } - catch (Exception e) - { - this.LogException(e, "Unhandled exception on connect"); - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Stop(); - ReconnectTimer.Interval = AutoReconnectIntervalMs; - ReconnectTimer.Start(); - } - } + this.LogDebug("Skipping connect because program is stopping"); + return; } - } - finally - { - connectLock.Release(); - } - } - /// - /// Disconnect method - /// - public void Disconnect() - { - ConnectEnabled = false; - // Stop trying reconnects, if we are - ReconnectTimer.Stop(); + ConnectEnabled = true; - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); - } - - /// - /// Kills the stream, cleans up the client and sets it to null - /// - private void KillClient(SocketStatus status) - { - KillStream(); - - try - { - if (Client != null) - { - Client.ErrorOccurred -= Client_ErrorOccurred; - Client.Disconnect(); - Client.Dispose(); - Client = null; - ClientStatus = status; - this.LogDebug("Disconnected"); - } - } - catch (Exception ex) - { - this.LogException(ex, "Exception in Kill Client"); - } - } - - /// - /// Kills the stream - /// - void KillStream() - { - try - { - if (TheStream != null) - { - TheStream.DataReceived -= Stream_DataReceived; - TheStream.Close(); - TheStream.Dispose(); - TheStream = null; - this.LogDebug("Disconnected stream"); - } - } - catch (Exception ex) - { - this.LogException(ex, "Exception in Kill Stream:{0}"); - } - } - - /// - /// Handles the keyboard interactive authentication, should it be required. - /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) - { - foreach (AuthenticationPrompt prompt in e.Prompts) - if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) - prompt.Response = Password; - } - - /// - /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. - /// - void Stream_DataReceived(object sender, ShellDataEventArgs e) - { - if (((ShellStream)sender).Length <= 0L) - { - return; - } - var response = ((ShellStream)sender).Read(); - - var bytesHandler = BytesReceived; - - if (bytesHandler != null) - { - var bytes = Encoding.UTF8.GetBytes(response); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - - var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response)); - - textHandler(this, new GenericCommMethodReceiveTextArgs(response)); - } - - } - - - /// - /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange - /// event - /// - void Client_ErrorOccurred(object sender, ExceptionEventArgs e) - { - System.Threading.Tasks.Task.Run(() => - { - if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) - this.LogError("Disconnected by remote"); - else - this.LogException(e.Exception, "Unhandled SSH client error"); try { connectLock.Wait(); - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); + if (IsConnected) + { + this.LogDebug("Connection already connected. Exiting Connect"); + } + else + { + this.LogDebug("Attempting connect"); + + // Cancel reconnect if running. + StopReconnectTimer(); + + // Cleanup the old client if it already exists + if (client != null) + { + this.LogDebug("Cleaning up disconnected client"); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + + // This handles both password and keyboard-interactive (like on OS-X, 'nixes) + KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); + kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); + PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); + + this.LogDebug("Creating new SshClient"); + ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); + client = new SshClient(connectionInfo); + client.ErrorOccurred += Client_ErrorOccurred; + + //Attempt to connect + ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; + try + { + client.Connect(); + + var modes = new Dictionary(); + + if (DisableEcho) + { + modes.Add(TerminalModes.ECHO, 0); + } + + shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes); + if (shellStream.DataAvailable) + { + // empty the buffer if there is data + shellStream.Read(); + } + shellStream.DataReceived += Stream_DataReceived; + this.LogInformation("Connected"); + ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; + disconnectLogged = false; + } + catch (SshConnectionException e) + { + var ie = e.InnerException; // The details are inside, when present - remote can close the connection with no inner exception at all + + if (ie is System.Net.Sockets.SocketException socketException) + { + this.LogError("Connection failure: Cannot reach {host} on {port}", + Hostname, Port); + this.LogVerbose(socketException, "SocketException details: "); + } + else if (ie is SshAuthenticationException) + { + this.LogError("Authentication failure for username {userName}", Username); + this.LogVerbose(ie, "AuthenticationException details: "); + } + else + { + this.LogError("Error on connect: {error}", ie?.Message ?? e.Message); + this.LogVerbose(ie ?? e, "Exception details: "); + } + + disconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs); + StartReconnectTimer(); + } + } + catch (SshOperationTimeoutException ex) + { + this.LogWarning("Connection attempt timed out: {message}", ex.Message); + + disconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + StartReconnectTimer(); + } + } + catch (Exception e) + { + this.LogError("Unhandled exception on connect: {error}", e.Message); + this.LogVerbose(e, "Exception details: "); + disconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + StartReconnectTimer(); + } + } + } } finally { connectLock.Release(); } - if (AutoReconnect && ConnectEnabled) + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + ConnectEnabled = false; + // Stop trying reconnects, if we are + StopReconnectTimer(); + + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + + /// + /// Kills the stream, cleans up the client and sets it to null + /// + private void KillClient(SocketStatus status) + { + KillStream(); + + try { - this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Stop(); - ReconnectTimer.Interval = AutoReconnectIntervalMs; - ReconnectTimer.Start(); + if (client != null) + { + client.ErrorOccurred -= Client_ErrorOccurred; + client.Disconnect(); + client.Dispose(); + client = null; + ClientStatus = status; + this.LogDebug("Disconnected"); + } } - }); + catch (Exception ex) + { + this.LogException(ex, "Exception in Kill Client"); + } + } + + /// + /// Kills the stream + /// + private void KillStream() + { + try + { + if (shellStream != null) + { + shellStream.DataReceived -= Stream_DataReceived; + shellStream.Close(); + shellStream.Dispose(); + shellStream = null; + this.LogDebug("Disconnected stream"); + } + } + catch (Exception ex) + { + this.LogException(ex, "Exception in Kill Stream:{0}"); + } + } + + /// + /// Handles the keyboard interactive authentication, should it be required. + /// + private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + { + foreach (AuthenticationPrompt prompt in e.Prompts) + if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) + prompt.Response = Password; + } + + /// + /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. + /// + private void Stream_DataReceived(object sender, ShellDataEventArgs e) + { + if (((ShellStream)sender).Length <= 0L) + { + return; + } + var response = ((ShellStream)sender).Read(); + + var bytesHandler = BytesReceived; + + if (bytesHandler != null) + { + var bytes = Encoding.UTF8.GetBytes(response); + this.PrintReceivedBytes(bytes); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + + var textHandler = TextReceived; + if (textHandler != null) + { + this.PrintReceivedText(response); + + textHandler(this, new GenericCommMethodReceiveTextArgs(response)); + } + + } + + + /// + /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange + /// event + /// + private void Client_ErrorOccurred(object sender, ExceptionEventArgs e) + { + CrestronInvoke.BeginInvoke(o => + { + if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) + this.LogError("Disconnected by remote"); + else + this.LogException(e.Exception, "Unhandled SSH client error"); + try + { + connectLock.Wait(); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); + } + finally + { + connectLock.Release(); + } + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + StartReconnectTimer(); + } + }); + } + + /// + /// Helper for ConnectionChange event + /// + private void OnConnectionChange() + { + ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); + } + + #region IBasicCommunication Members + + /// + /// Sends text to the server + /// + /// The text to send + public void SendText(string text) + { + try + { + if (client != null && shellStream != null && IsConnected) + { + this.PrintSentText(text); + + shellStream.Write(text); + shellStream.Flush(); + } + else + { + this.LogDebug("Client is null or disconnected. Cannot Send Text"); + } + } + catch (ObjectDisposedException) + { + this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); + + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + StartReconnectTimer(); + } + } + catch (Exception ex) + { + this.LogException(ex, "Exception sending text: '{message}'", text); + } + } + + /// + /// Sends Bytes to the server + /// + /// The bytes to send + public void SendBytes(byte[] bytes) + { + try + { + if (client != null && shellStream != null && IsConnected) + { + this.PrintSentBytes(bytes); + + shellStream.Write(bytes, 0, bytes.Length); + shellStream.Flush(); + } + else + { + this.LogDebug("Client is null or disconnected. Cannot Send Bytes"); + } + } + catch (ObjectDisposedException ex) + { + this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); + + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect && ConnectEnabled && !_isProgramStopping) + { + StartReconnectTimer(); + } + } + catch (Exception ex) + { + this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes)); + } + } + #endregion + + /// + /// Safely starts the reconnect timer with exception handling + /// + private void StartReconnectTimer() + { + try + { + reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to start timer but it was already disposed"); + } + } + + /// + /// Safely stops the reconnect timer with exception handling + /// + private void StopReconnectTimer() + { + try + { + reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // Timer was disposed, ignore + this.LogDebug("Attempted to stop timer but it was already disposed"); + } + } + + /// + /// Deactivate method - properly dispose of resources + /// + public override bool Deactivate() + { + try + { + this.LogDebug("Deactivating SSH client - disposing resources"); + + // Stop trying reconnects + ConnectEnabled = false; + StopReconnectTimer(); + + // Disconnect and cleanup client + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + + // Dispose timer + try + { + reconnectTimer?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + // Dispose semaphore + try + { + connectLock?.Dispose(); + } + catch (ObjectDisposedException) + { + // Already disposed, ignore + } + + return base.Deactivate(); + } + catch (Exception ex) + { + this.LogException(ex, "Error during SSH client deactivation"); + return false; + } + } + } - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - if (ConnectionChange != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - - #region IBasicCommunication Members - - /// - /// Sends text to the server - /// - /// - public void SendText(string text) - { - try - { - if (Client != null && TheStream != null && IsConnected) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation( - "Sending {length} characters of text: '{text}'", - text.Length, - ComTextHelper.GetDebugText(text)); - - TheStream.Write(text); - TheStream.Flush(); - } - else - { - this.LogDebug("Client is null or disconnected. Cannot Send Text"); - } - } - catch (ObjectDisposedException) - { - this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim()); - - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Stop(); - ReconnectTimer.Start(); - } - catch (Exception ex) - { - this.LogException(ex, "Exception sending text: '{message}'", text); - } - } - - /// - /// Sends Bytes to the server - /// - /// - public void SendBytes(byte[] bytes) - { - try - { - if (Client != null && TheStream != null && IsConnected) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - - TheStream.Write(bytes, 0, bytes.Length); - TheStream.Flush(); - } - else - { - this.LogDebug("Client is null or disconnected. Cannot Send Bytes"); - } - } - catch (ObjectDisposedException ex) - { - this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes)); - - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Stop(); - ReconnectTimer.Start(); - } - catch (Exception ex) - { - this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes)); - } - } - #endregion - //***************************************************************************************************** //***************************************************************************************************** /// - /// Fired when connection changes + /// Represents a SshConnectionChangeEventArgs /// public class SshConnectionChangeEventArgs : EventArgs { /// /// Connection State /// - public bool IsConnected { get; private set; } + public bool IsConnected { get; private set; } /// - /// Connection Status represented as a ushort + /// Gets or sets the UIsConnected /// public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } /// - /// The client + /// Gets or sets the Client /// public GenericSshClient Client { get; private set; } /// - /// Socket Status as represented by + /// Gets or sets the Status /// public ushort Status { get { return Client.UStatus; } } @@ -619,7 +680,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR /// /// Connection State /// The Client - public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client) + public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client) { IsConnected = isConnected; Client = client; diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs index 54298cc3..d13eed3f 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient.cs @@ -1,552 +1,569 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using Timer = System.Timers.Timer; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; -using Required = NewtonsoftJson::Newtonsoft.Json.Required; -using PepperDash.Core.Logging; -using System.Threading.Tasks; +using Newtonsoft.Json; -namespace PepperDash.Core; - -/// -/// A class to handle basic TCP/IP communications with a server -/// -public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect +namespace PepperDash.Core { - private const string SplusKey = "Uninitialized TcpIpClient"; /// - /// Object to enable stream debugging + /// A class to handle basic TCP/IP communications with a server /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; - - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; - - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; - - - private string _hostname; - - /// - /// Address of server - /// - public string Hostname + public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { - get + private const string SplusKey = "Uninitialized TcpIpClient"; + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; + + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; + + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; + + + private string _hostname; + + /// + /// Address of server + /// + public string Hostname { - return _hostname; + get + { + return _hostname; + } + + set + { + _hostname = value; + if (_client != null) + { + _client.AddressClientConnectedTo = _hostname; + } + } } - set + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// Another damn S+ helper because S+ seems to treat large port nums as signed ints + /// which screws up things + /// + public ushort UPort { - _hostname = value; + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// The actual client class + /// + private TCPClient _client; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// Ushort representation of client status + /// + [Obsolete] + public ushort UClientStatus { get { return (ushort)ClientStatus; } } + + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } + + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Set only when the disconnect method is called + /// + bool DisconnectCalledByUser; + + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + //Lock object to prevent simulatneous connect/disconnect operations + private CCriticalSection connectLock = new CCriticalSection(); + + // private Timer for auto reconnect + private CTimer RetryTimer; + + /// + /// Constructor + /// + /// unique string to differentiate between instances + /// + /// + /// + public GenericTcpIpClient(string key, string address, int port, int bufferSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + Hostname = address; + Port = port; + BufferSize = bufferSize; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Constructor + /// + /// + public GenericTcpIpClient(string key) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Default constructor for S+ + /// + public GenericTcpIpClient() + : base(SplusKey) + { + StreamDebugging = new CommunicationStreamDebugging(SplusKey); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + Debug.Console(1, this, "Program stopping. Closing connection"); + Deactivate(); + } + } + + /// + /// + /// + /// + /// + /// Deactivate method + /// + public override bool Deactivate() + { + RetryTimer.Stop(); + RetryTimer.Dispose(); if (_client != null) { - _client.AddressClientConnectedTo = _hostname; + _client.SocketStatusChange -= this.Client_SocketStatusChange; + DisconnectClient(); } + return true; } - } - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// Another damn S+ helper because S+ seems to treat large port nums as signed ints - /// which screws up things - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// The actual client class - /// - private TCPClient _client; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } - - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Set only when the disconnect method is called - /// - bool DisconnectCalledByUser; - - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - //Lock object to prevent simulatneous connect/disconnect operations - private readonly object _connectLock = new(); - - // private Timer for auto reconnect - private Timer RetryTimer; - - /// - /// Constructor - /// - /// unique string to differentiate between instances - /// - /// - /// - public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - Hostname = address; - Port = port; - BufferSize = bufferSize; - - SetupRetryTimer(); - } - - /// - /// Constructor - /// - /// - public GenericTcpIpClient(string key) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - - SetupRetryTimer(); - } - - /// - /// Default constructor for S+ - /// - public GenericTcpIpClient() - : base(SplusKey) - { - StreamDebugging = new CommunicationStreamDebugging(SplusKey); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - - SetupRetryTimer(); - } - - private void SetupRetryTimer() - { - RetryTimer = new Timer { AutoReset = false, Enabled = false }; - RetryTimer.Elapsed += (s, e) => Reconnect(); - } - - - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - this.LogInformation("Program stopping. Closing connection"); - Deactivate(); - } - } - - /// - /// - /// - /// - public override bool Deactivate() - { - RetryTimer.Stop(); - RetryTimer.Dispose(); - if (_client != null) - { - _client.SocketStatusChange -= this.Client_SocketStatusChange; - DisconnectClient(); - } - return true; - } - - /// - /// Attempts to connect to the server - /// - public void Connect() - { - if (string.IsNullOrEmpty(Hostname)) - { - this.LogWarning("GenericTcpIpClient '{0}': No address set", Key); - return; - } - if (Port < 1 || Port > 65535) + /// + /// Connect method + /// + public void Connect() { + if (string.IsNullOrEmpty(Hostname)) { - this.LogWarning("GenericTcpIpClient '{0}': Invalid port", Key); + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); return; } - } - - lock (_connectLock) - { - if (IsConnected) + if (Port < 1 || Port > 65535) { - this.LogInformation("Connection already connected. Exiting Connect()"); - } - else - { - //Stop retry timer if running - RetryTimer.Stop(); - _client = new TCPClient(Hostname, Port, BufferSize); - _client.SocketStatusChange -= Client_SocketStatusChange; - _client.SocketStatusChange += Client_SocketStatusChange; - DisconnectCalledByUser = false; - _client.ConnectToServerAsync(ConnectToServerCallback); - } - } - } - - private void Reconnect() - { - if (_client == null) - { - return; - } - lock (_connectLock) - { - if (IsConnected || DisconnectCalledByUser == true) - { - this.LogInformation("Reconnect no longer needed. Exiting Reconnect()"); - } - else - { - this.LogInformation("Attempting reconnect now"); - _client.ConnectToServerAsync(ConnectToServerCallback); - } - } - } - - /// - /// Attempts to disconnect the client - /// - public void Disconnect() - { - lock (_connectLock) - { - DisconnectCalledByUser = true; - - // Stop trying reconnects, if we are - RetryTimer.Stop(); - DisconnectClient(); - } - } - - /// - /// Does the actual disconnect business - /// - public void DisconnectClient() - { - if (_client != null) - { - this.LogInformation("Disconnecting client"); - if (IsConnected) - _client.DisconnectFromServer(); - } - } - - /// - /// Callback method for connection attempt - /// - /// - void ConnectToServerCallback(TCPClient c) - { - if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - this.LogInformation("Server connection result: {0}", c.ClientStatus); - WaitAndTryReconnect(); - } - else - { - this.LogInformation("Server connection result: {0}", c.ClientStatus); - } - } - - /// - /// Disconnects, waits and attemtps to connect again - /// - void WaitAndTryReconnect() - { - Task.Run(() => - { - lock (_connectLock) - { - if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null) { - DisconnectClient(); - this.LogInformation("Attempting reconnect, status={0}", _client.ClientStatus); + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key); + return; + } + } + + try + { + connectLock.Enter(); + if (IsConnected) + { + Debug.Console(1, this, "Connection already connected. Exiting Connect()"); + } + else + { + //Stop retry timer if running RetryTimer.Stop(); - RetryTimer.Interval = AutoReconnectIntervalMs; - RetryTimer.Start(); + _client = new TCPClient(Hostname, Port, BufferSize); + _client.SocketStatusChange -= Client_SocketStatusChange; + _client.SocketStatusChange += Client_SocketStatusChange; + DisconnectCalledByUser = false; + _client.ConnectToServerAsync(ConnectToServerCallback); } } - }); - } - - /// - /// Recieves incoming data - /// - /// - /// - void Receive(TCPClient client, int numBytes) - { - if (client != null) - { - if (numBytes > 0) + finally { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); - } - - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } + connectLock.Leave(); } - client.ReceiveDataAsync(Receive); } - } - /// - /// General send method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); - if (_client != null) - _client.SendData(bytes, bytes.Length); - } - - /// - /// SendEscapedText method - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + private void Reconnect() + { + if (_client == null) { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); + return; + } + try + { + connectLock.Enter(); + if (IsConnected || DisconnectCalledByUser == true) + { + Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()"); + } + else + { + Debug.Console(1, this, "Attempting reconnect now"); + _client.ConnectToServerAsync(ConnectToServerCallback); + } + } + finally + { + connectLock.Leave(); + } + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + try + { + connectLock.Enter(); + DisconnectCalledByUser = true; + + // Stop trying reconnects, if we are + RetryTimer.Stop(); + DisconnectClient(); + } + finally + { + connectLock.Leave(); + } + } + + /// + /// DisconnectClient method + /// + public void DisconnectClient() + { + if (_client != null) + { + Debug.Console(1, this, "Disconnecting client"); + if (IsConnected) + _client.DisconnectFromServer(); + } + } + + /// + /// Callback method for connection attempt + /// + /// + void ConnectToServerCallback(TCPClient c) + { + if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); + WaitAndTryReconnect(); + } + else + { + Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); + } + } + + /// + /// Disconnects, waits and attemtps to connect again + /// + void WaitAndTryReconnect() + { + CrestronInvoke.BeginInvoke(o => + { + try + { + connectLock.Enter(); + if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null) + { + DisconnectClient(); + Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus); + RetryTimer.Reset(AutoReconnectIntervalMs); + } + } + finally + { + connectLock.Leave(); + } }); - SendText(unescapedText); - } - - /// - /// Sends Bytes to the server - /// - /// - public void SendBytes(byte[] bytes) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - if (_client != null) - _client.SendData(bytes, bytes.Length); - } - - /// - /// Socket Status Change Handler - /// - /// - /// - void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { - if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - WaitAndTryReconnect(); - } - else - { - this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - _client.ReceiveDataAsync(Receive); } - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + /// + /// Recieves incoming data + /// + /// + /// + void Receive(TCPClient client, int numBytes) + { + if (client != null) + { + if (numBytes > 0) + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + this.PrintReceivedBytes(bytes); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + + this.PrintReceivedText(str); + + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + client.ReceiveDataAsync(Receive); + } + } + + /// + /// SendText method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array + this.PrintSentText(text); + if (_client != null) + _client.SendData(bytes, bytes.Length); + } + + /// + /// SendEscapedText method + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + } + + /// + /// Sends Bytes to the server + /// + /// + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + this.PrintSentBytes(bytes); + if (_client != null) + _client.SendData(bytes, bytes.Length); + } + + /// + /// Socket Status Change Handler + /// + /// + /// + void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) + { + if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); + WaitAndTryReconnect(); + } + else + { + Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); + _client.ReceiveDataAsync(Receive); + } + + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } } -} - -/// -/// Configuration properties for TCP/SSH Connections -/// -public class TcpSshPropertiesConfig -{ - /// - /// Address to connect to - /// - [JsonProperty(Required = Required.Always)] - public string Address { get; set; } /// - /// Port to connect to + /// Represents a TcpSshPropertiesConfig /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - - /// - /// Username credential - /// - public string Username { get; set; } - /// - /// Passord credential - /// - public string Password { get; set; } - - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } - - /// - /// Gets or sets the AutoReconnect - /// - public bool AutoReconnect { get; set; } - - /// - /// Gets or sets the AutoReconnectIntervalMs - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// When true, turns off echo for the SSH session - /// - [JsonProperty("disableSshEcho")] - public bool DisableSshEcho { get; set; } - - /// - /// Default constructor - /// - public TcpSshPropertiesConfig() + public class TcpSshPropertiesConfig { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; - Username = ""; - Password = ""; - } + /// + /// Address to connect to + /// + [JsonProperty(Required = Required.Always)] + public string Address { get; set; } + /// + /// Port to connect to + /// + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + + /// + /// Username credential + /// + public string Username { get; set; } + /// + /// Gets or sets the Password + /// + public string Password { get; set; } + + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } + + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } + + /// + /// Gets or sets the AutoReconnectIntervalMs + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// When true, turns off echo for the SSH session + /// + [JsonProperty("disableSshEcho")] + public bool DisableSshEcho { get; set; } + + /// + /// Default constructor + /// + public TcpSshPropertiesConfig() + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; + Username = ""; + Password = ""; + DisableSshEcho = false; + } + } } diff --git a/src/PepperDash.Core/Comm/GenericTcpIpClient_ForServer.cs b/src/PepperDash.Core/Comm/GenericTcpIpClient_ForServer.cs index 08259f6e..6d4958ca 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpClient_ForServer.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpClient_ForServer.cs @@ -11,781 +11,765 @@ PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------ */ using System; +using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; +using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; -namespace PepperDash.Core; - -/// -/// Generic TCP/IP client for server -/// -public class GenericTcpIpClient_ForServer : Device, IAutoReconnect +namespace PepperDash.Core { /// - /// Band aid delegate for choked server + /// Generic TCP/IP client for server /// - internal delegate void ConnectionHasHungCallbackDelegate(); - - #region Events - - //public event EventHandler BytesReceived; - - /// - /// Notifies of text received - /// - public event EventHandler TextReceived; - - /// - /// Notifies of socket status change - /// - public event EventHandler ConnectionChange; - - - /// - /// This is something of a band-aid callback. If the client times out during the connection process, because the server - /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help - /// keep a watch on the server and reset it if necessary. - /// - internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; - - /// - /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require - /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. - /// - public event EventHandler ClientReadyForCommunications; - - #endregion - - #region Properties & Variables - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// S+ helper - /// - public ushort UPort + public class GenericTcpIpClient_ForServer : Device, IAutoReconnect { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } + /// + /// Band aid delegate for choked server + /// + internal delegate void ConnectionHasHungCallbackDelegate(); - /// - /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class - /// - public bool SharedKeyRequired { get; set; } + #region Events - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set + //public event EventHandler BytesReceived; + + /// + /// Notifies of text received + /// + public event EventHandler TextReceived; + + /// + /// Notifies of socket status change + /// + public event EventHandler ConnectionChange; + + + /// + /// This is something of a band-aid callback. If the client times out during the connection process, because the server + /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help + /// keep a watch on the server and reset it if necessary. + /// + internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; + + /// + /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require + /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. + /// + public event EventHandler ClientReadyForCommunications; + + #endregion + + #region Properties & Variables + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// S+ helper + /// + public ushort UPort { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module - /// - public string SharedKey { get; set; } - - /// - /// flag to show the client is waiting for the server to send the shared key - /// - private bool WaitingForSharedKeyResponse { get; set; } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Semaphore on connect method - /// - bool IsTryingToConnect; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (Client != null) - return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; - else - return false; - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is ready for communication after shared key exchange - /// - public bool IsReadyForCommunication { get; set; } - - /// - /// S+ helper for IsReadyForCommunication - /// - public ushort UIsReadyForCommunication - { - get { return (ushort)(IsReadyForCommunication ? 1 : 0); } - } - - /// - /// Client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - if (Client != null) - return Client.ClientStatus; - else - return SocketStatus.SOCKET_STATUS_NO_CONNECT; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Flag Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// private Timer for auto reconnect - /// - Timer RetryTimer; - - - /// - /// - /// - public bool HeartbeatEnabled { get; set; } - - /// - /// - /// - public ushort UHeartbeatEnabled - { - get { return (ushort)(HeartbeatEnabled ? 1 : 0); } - set { HeartbeatEnabled = value == 1; } - } - - /// - /// - /// - public string HeartbeatString = "heartbeat"; - - /// - /// - /// - public int HeartbeatInterval = 50000; - - Timer HeartbeatSendTimer; - Timer HeartbeatAckTimer; - /// - /// Used to force disconnection on a dead connect attempt - /// - Timer ConnectFailTimer; - Timer WaitForSharedKey; - private int ConnectionCount; - /// - /// Internal secure client - /// - TCPClient Client; - - bool ProgramIsStopping; - - #endregion - - #region Constructors - - /// - /// Constructor - /// - /// - /// - /// - /// - public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize) - : base(key) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Hostname = address; - Port = port; - BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; - - } - - /// - /// Constructor for S+ - /// - public GenericTcpIpClient_ForServer() - : base("Uninitialized DynamicTcpClient") - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - } - #endregion - - #region Methods - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) - { - this.LogInformation("Program stopping. Closing Client connection"); - ProgramIsStopping = true; - Disconnect(); + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } } - } + /// + /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class + /// + public bool SharedKeyRequired { get; set; } - /// - /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. - /// - public void Connect() - { - ConnectionCount++; - this.LogDebug("Attempting connect Count:{0}", ConnectionCount); - - - if (IsConnected) + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired { - this.LogInformation("Already connected. Ignoring."); - return; - } - if (IsTryingToConnect) - { - this.LogInformation("Already trying to connect. Ignoring."); - return; - } - try - { - IsTryingToConnect = true; - if (RetryTimer != null) + set { - RetryTimer.Stop(); - RetryTimer = null; - } - if (string.IsNullOrEmpty(Hostname)) - { - this.LogWarning("DynamicTcpClient: No address set"); - return; - } - if (Port < 1 || Port > 65535) - { - this.LogWarning("DynamicTcpClient: Invalid port"); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - this.LogWarning("DynamicTcpClient: No Shared Key set"); - return; - } - - // clean up previous client - if (Client != null) - { - Cleanup(); - } - DisconnectCalledByUser = false; - - Client = new TCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange += Client_SocketStatusChange; - if(HeartbeatEnabled) - Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); - Client.AddressClientConnectedTo = Hostname; - Client.PortNumber = Port; - // SecureClient = c; - - //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); - - ConnectFailTimer = new Timer(30000) { AutoReset = false }; - ConnectFailTimer.Elapsed += (s, e) => - { - this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); - if (IsTryingToConnect) - { - IsTryingToConnect = false; - - //if (ConnectionHasHungCallback != null) - //{ - // ConnectionHasHungCallback(); - //} - //SecureClient.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - } - }; - ConnectFailTimer.Start(); - - this.LogDebug("Making Connection Count:{0}", ConnectionCount); - Client.ConnectToServerAsync(o => - { - this.LogDebug("ConnectToServerAsync Count:{0} Ran!", ConnectionCount); - - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - } - IsTryingToConnect = false; - - if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); - o.ReceiveDataAsync(Receive); - - if (SharedKeyRequired) - { - WaitingForSharedKeyResponse = true; - WaitForSharedKey = new Timer(15000) { AutoReset = false }; - WaitForSharedKey.Elapsed += (s, e) => - { - this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); - // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup - o.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - //OnClientReadyForcommunications(false); // Should send false event - }; - WaitForSharedKey.Start(); - } - else - { - //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key - //required this is called by the shared key being negotiated - if (IsReadyForCommunication == false) - { - OnClientReadyForcommunications(true); // Key not required - } - } - } + if (value == 1) + SharedKeyRequired = true; else - { - this.LogWarning("Connect attempt failed {0}", o.ClientStatus); - CheckClosedAndTryReconnect(); - } - }); - } - catch (Exception ex) - { - this.LogException(ex, "Client connection exception: {0}", ex.Message); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); - IsTryingToConnect = false; - CheckClosedAndTryReconnect(); - } - } - - /// - /// - /// - public void Disconnect() - { - this.LogVerbose("Disconnect Called"); - - DisconnectCalledByUser = true; - if (IsConnected) - { - Client.DisconnectFromServer(); - - } - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - Cleanup(); - } - - /// - /// Internal call to close up client. ALWAYS use this when disconnecting. - /// - void Cleanup() - { - IsTryingToConnect = false; - - if (Client != null) - { - //SecureClient.DisconnectFromServer(); - this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.Dispose(); - Client = null; - } - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - ConnectFailTimer.Dispose(); - ConnectFailTimer = null; - } - } - - - /// ff - /// Called from Connect failure or Socket Status change if - /// auto reconnect and socket disconnected (Not disconnected by user) - /// - void CheckClosedAndTryReconnect() - { - if (Client != null) - { - this.LogVerbose("Cleaning up remotely closed/failed connection."); - Cleanup(); - } - if (!DisconnectCalledByUser && AutoReconnect) - { - var halfInterval = AutoReconnectIntervalMs / 2; - var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; - this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime); - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - RetryTimer = new Timer(rndTime) { AutoReset = false }; - RetryTimer.Elapsed += (s, e) => Connect(); - RetryTimer.Start(); - } - } - - /// - /// Receive callback - /// - /// - /// - void Receive(TCPClient client, int numBytes) - { - if (numBytes > 0) - { - string str = string.Empty; - - try - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - this.LogVerbose("Client Received:\r--------\r{0}\r--------", str); - if (!string.IsNullOrEmpty(checkHeartbeat(str))) - { - if (SharedKeyRequired && str == "SharedKey:") - { - this.LogVerbose("Server asking for shared key, sending"); - SendText(SharedKey + "\n"); - } - else if (SharedKeyRequired && str == "Shared Key Match") - { - StopWaitForSharedKeyTimer(); - this.LogVerbose("Shared key confirmed. Ready for communication"); - OnClientReadyForcommunications(true); // Successful key exchange - } - else - { - //var bytesHandler = BytesReceived; - //if (bytesHandler != null) - // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); - } - } - } - catch (Exception ex) - { - this.LogException(ex, "Error receiving data: {1}. Error: {0}", ex.Message, str); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); - } - } - if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - client.ReceiveDataAsync(Receive); - } - - void HeartbeatStart() - { - if (HeartbeatEnabled) - { - this.LogVerbose("Starting Heartbeat"); - if (HeartbeatSendTimer == null) - { - - HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true }; - HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null); - HeartbeatSendTimer.Start(); - } - if (HeartbeatAckTimer == null) - { - HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); + SharedKeyRequired = false; } } - } - void HeartbeatStop() - { + /// + /// Gets or sets the SharedKey + /// + public string SharedKey { get; set; } - if (HeartbeatSendTimer != null) + /// + /// flag to show the client is waiting for the server to send the shared key + /// + private bool WaitingForSharedKeyResponse { get; set; } + + /// + /// Gets or sets the BufferSize + /// + public int BufferSize { get; set; } + + /// + /// Semaphore on connect method + /// + bool IsTryingToConnect; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected { - this.LogVerbose("Stoping Heartbeat Send"); - HeartbeatSendTimer.Stop(); - HeartbeatSendTimer = null; - } - if (HeartbeatAckTimer != null) - { - this.LogVerbose("Stoping Heartbeat Ack"); - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer = null; - } - - } - void SendHeartbeat(object notused) - { - this.SendText(HeartbeatString); - this.LogVerbose("Sending Heartbeat"); - - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(string received) - { - try - { - if (HeartbeatEnabled) + get { - if (!string.IsNullOrEmpty(HeartbeatString)) - { - var remainingText = received.Replace(HeartbeatString, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatString)) - { - if (HeartbeatAckTimer != null) - { - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer.Interval = HeartbeatInterval * 2; - HeartbeatAckTimer.Start(); - } - else - { - HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true }; - HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null); - HeartbeatAckTimer.Start(); - } - this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString); - return remainingText; - } - } + if (Client != null) + return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; + else + return false; } } - catch (Exception ex) + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected { - this.LogException(ex, "Error checking heartbeat: {0}", ex.Message); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); + get { return (ushort)(IsConnected ? 1 : 0); } } - return received; - } + /// + /// Bool showing if socket is ready for communication after shared key exchange + /// + public bool IsReadyForCommunication { get; set; } - - void HeartbeatAckTimerFail(object o) - { - try + /// + /// S+ helper for IsReadyForCommunication + /// + public ushort UIsReadyForCommunication { + get { return (ushort)(IsReadyForCommunication ? 1 : 0); } + } + + /// + /// Client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + if (Client != null) + return Client.ClientStatus; + else + return SocketStatus.SOCKET_STATUS_NO_CONNECT; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// bool to track if auto reconnect should be set on the socket + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Flag Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// private Timer for auto reconnect + /// + CTimer RetryTimer; + + + /// + /// + /// + public bool HeartbeatEnabled { get; set; } + + /// + /// + /// + public ushort UHeartbeatEnabled + { + get { return (ushort)(HeartbeatEnabled ? 1 : 0); } + set { HeartbeatEnabled = value == 1; } + } + + /// + /// + /// + public string HeartbeatString = "heartbeat"; + + /// + /// + /// + public int HeartbeatInterval = 50000; + + CTimer HeartbeatSendTimer; + CTimer HeartbeatAckTimer; + /// + /// Used to force disconnection on a dead connect attempt + /// + CTimer ConnectFailTimer; + CTimer WaitForSharedKey; + private int ConnectionCount; + /// + /// Internal secure client + /// + TCPClient Client; + + bool ProgramIsStopping; + + #endregion + + #region Constructors + + /// + /// Constructor + /// + /// + /// + /// + /// + public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize) + : base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Hostname = address; + Port = port; + BufferSize = bufferSize; + AutoReconnectIntervalMs = 5000; + + } + + /// + /// Constructor for S+ + /// + public GenericTcpIpClient_ForServer() + : base("Uninitialized DynamicTcpClient") + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + } + #endregion + + #region Methods + + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); + ProgramIsStopping = true; + Disconnect(); + } + + } + + /// + /// Connect method + /// + public void Connect() + { + ConnectionCount++; + Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); + if (IsConnected) { - this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); - SendText("Heartbeat not received by server, closing connection"); + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); + return; + } + if (IsTryingToConnect) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); + return; + } + try + { + IsTryingToConnect = true; + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); + return; + } + if (Port < 1 || Port > 65535) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); + return; + } + + // clean up previous client + if (Client != null) + { + Cleanup(); + } + DisconnectCalledByUser = false; + + Client = new TCPClient(Hostname, Port, BufferSize); + Client.SocketStatusChange += Client_SocketStatusChange; + if(HeartbeatEnabled) + Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); + Client.AddressClientConnectedTo = Hostname; + Client.PortNumber = Port; + // SecureClient = c; + + //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); + + ConnectFailTimer = new CTimer(o => + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); + if (IsTryingToConnect) + { + IsTryingToConnect = false; + + //if (ConnectionHasHungCallback != null) + //{ + // ConnectionHasHungCallback(); + //} + //SecureClient.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + } + }, 30000); + + Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); + Client.ConnectToServerAsync(o => + { + Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + } + IsTryingToConnect = false; + + if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); + o.ReceiveDataAsync(Receive); + + if (SharedKeyRequired) + { + WaitingForSharedKeyResponse = true; + WaitForSharedKey = new CTimer(timer => + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); + // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); + // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup + o.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + //OnClientReadyForcommunications(false); // Should send false event + }, 15000); + } + else + { + //CLient connected and shared key is not required so just raise the ready for communication event. if Shared key + //required this is called by the shared key being negotiated + if (IsReadyForCommunication == false) + { + OnClientReadyForcommunications(true); // Key not required + } + } + } + else + { + Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); + CheckClosedAndTryReconnect(); + } + }); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); + IsTryingToConnect = false; CheckClosedAndTryReconnect(); } + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + this.LogVerbose("Disconnect Called"); + + DisconnectCalledByUser = true; + if (IsConnected) + { + Client.DisconnectFromServer(); + + } + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + Cleanup(); + } + + /// + /// Internal call to close up client. ALWAYS use this when disconnecting. + /// + void Cleanup() + { + IsTryingToConnect = false; + + if (Client != null) + { + //SecureClient.DisconnectFromServer(); + Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); + Client.SocketStatusChange -= Client_SocketStatusChange; + Client.Dispose(); + Client = null; + } + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + ConnectFailTimer.Dispose(); + ConnectFailTimer = null; + } + } + + + /// ff + /// Called from Connect failure or Socket Status change if + /// auto reconnect and socket disconnected (Not disconnected by user) + /// + void CheckClosedAndTryReconnect() + { + if (Client != null) + { + Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); + Cleanup(); + } + if (!DisconnectCalledByUser && AutoReconnect) + { + var halfInterval = AutoReconnectIntervalMs / 2; + var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; + Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + RetryTimer = new CTimer(o => Connect(), rndTime); + } + } + + /// + /// Receive callback + /// + /// + /// + void Receive(TCPClient client, int numBytes) + { + if (numBytes > 0) + { + string str = string.Empty; + + try + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); + if (!string.IsNullOrEmpty(checkHeartbeat(str))) + { + if (SharedKeyRequired && str == "SharedKey:") + { + Debug.Console(2, this, "Server asking for shared key, sending"); + SendText(SharedKey + "\n"); + } + else if (SharedKeyRequired && str == "Shared Key Match") + { + StopWaitForSharedKeyTimer(); + Debug.Console(2, this, "Shared key confirmed. Ready for communication"); + OnClientReadyForcommunications(true); // Successful key exchange + } + else + { + //var bytesHandler = BytesReceived; + //if (bytesHandler != null) + // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); + } + } + if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + client.ReceiveDataAsync(Receive); + } + + void HeartbeatStart() + { + if (HeartbeatEnabled) + { + Debug.Console(2, this, "Starting Heartbeat"); + if (HeartbeatSendTimer == null) + { + + HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); + } + if (HeartbeatAckTimer == null) + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + } } - catch (Exception ex) + void HeartbeatStop() { - this.LogException(ex, "Heartbeat timeout Error on Client: {0}, {1}", Key, ex.Message); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); - } - } - /// - /// - /// - void StopWaitForSharedKeyTimer() - { - if (WaitForSharedKey != null) + if (HeartbeatSendTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Send"); + HeartbeatSendTimer.Stop(); + HeartbeatSendTimer = null; + } + if (HeartbeatAckTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Ack"); + HeartbeatAckTimer.Stop(); + HeartbeatAckTimer = null; + } + + } + void SendHeartbeat(object notused) { - WaitForSharedKey.Stop(); - WaitForSharedKey = null; - } - } + this.SendText(HeartbeatString); + Debug.Console(2, this, "Sending Heartbeat"); - /// - /// General send method - /// - public void SendText(string text) - { - if (!string.IsNullOrEmpty(text)) + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(string received) { try { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + if (HeartbeatEnabled) { - Client.SendDataAsync(bytes, bytes.Length, (c, n) => + if (!string.IsNullOrEmpty(HeartbeatString)) { - // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? - if (n <= 0) + var remainingText = received.Replace(HeartbeatString, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatString)) { - this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key); + if (HeartbeatAckTimer != null) + { + HeartbeatAckTimer.Reset(HeartbeatInterval * 2); + } + else + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); + return remainingText; } - }); + } } } catch (Exception ex) { - this.LogException(ex, "Error sending text: {1}. Error: {0}", ex.Message, text); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); } + return received; } - } - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (bytes.Length > 0) + + + void HeartbeatAckTimerFail(object o) { try { - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - Client.SendData(bytes, bytes.Length); + + if (IsConnected) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); + SendText("Heartbeat not received by server, closing connection"); + CheckClosedAndTryReconnect(); + } + } catch (Exception ex) { - this.LogException(ex, "Error sending bytes. Error: {0}", ex.Message); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); + ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); } } - } - /// - /// SocketStatusChange Callback - /// - /// - /// - void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { - if (ProgramIsStopping) + /// + /// + /// + void StopWaitForSharedKeyTimer() { - ProgramIsStopping = false; - return; - } - try - { - this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); - - OnConnectionChange(); - - // The client could be null or disposed by this time... - if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + if (WaitForSharedKey != null) { - HeartbeatStop(); - OnClientReadyForcommunications(false); // socket has gone low - CheckClosedAndTryReconnect(); + WaitForSharedKey.Stop(); + WaitForSharedKey = null; } } - catch (Exception ex) + + /// + /// SendText method + /// + public void SendText(string text) { - this.LogException(ex, "Error in socket status change callback. Error: {0}", ex.Message); - this.LogVerbose("Stack Trace: {0}", ex.StackTrace); + if (!string.IsNullOrEmpty(text)) + { + try + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Client.SendDataAsync(bytes, bytes.Length, (c, n) => + { + // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? + if (n <= 0) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); + } + }); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); + } + } } - } - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); - } + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + if (bytes.Length > 0) + { + try + { + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + Client.SendData(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); + } + } + } - /// - /// Helper to fire ClientReadyForCommunications event - /// - void OnClientReadyForcommunications(bool isReady) - { - IsReadyForCommunication = isReady; - if (this.IsReadyForCommunication) { HeartbeatStart(); } - var handler = ClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + /// + /// SocketStatusChange Callback + /// + /// + /// + void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) + { + if (ProgramIsStopping) + { + ProgramIsStopping = false; + return; + } + try + { + Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); + + OnConnectionChange(); + + // The client could be null or disposed by this time... + if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + HeartbeatStop(); + OnClientReadyForcommunications(false); // socket has gone low + CheckClosedAndTryReconnect(); + } + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + } + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); + } + + /// + /// Helper to fire ClientReadyForCommunications event + /// + void OnClientReadyForcommunications(bool isReady) + { + IsReadyForCommunication = isReady; + if (this.IsReadyForCommunication) { HeartbeatStart(); } + var handler = ClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + } + #endregion } - #endregion -} + +} \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericTcpIpServer.cs b/src/PepperDash.Core/Comm/GenericTcpIpServer.cs index 3d5d5a0c..5cec96df 100644 --- a/src/PepperDash.Core/Comm/GenericTcpIpServer.cs +++ b/src/PepperDash.Core/Comm/GenericTcpIpServer.cs @@ -13,798 +13,795 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using PepperDash.Core.Logging; -namespace PepperDash.Core; - -/// -/// Generic TCP/IP server device -/// -public class GenericTcpIpServer : Device +namespace PepperDash.Core { - #region Events /// - /// Event for Receiving text + /// Generic TCP/IP server device /// - public event EventHandler TextReceived; - - /// - /// Event for client connection socket status change - /// - public event EventHandler ClientConnectionChange; - - /// - /// Event for Server State Change - /// - public event EventHandler ServerStateChange; - - /// - /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire - /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. - /// - public event EventHandler ServerClientReadyForCommunications; - - /// - /// A band aid event to notify user that the server has choked. - /// - public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } - - /// - /// - /// - public delegate void ServerHasChokedCallbackDelegate(); - - #endregion - - #region Properties/Variables - - /// - /// Server listen lock - /// - object _serverLock = new(); - - /// - /// Broadcast lock - /// - private readonly object _broadcastLock = new(); - - - /// - /// A bandaid client that monitors whether the server is reachable - /// - GenericTcpIpClient_ForServer MonitorClient; - - /// - /// Timer to operate the bandaid monitor client in a loop. - /// - Timer MonitorClientTimer; - - /// - /// - /// - int MonitorClientFailureCount; - - /// - /// 3 by default - /// - public int MonitorClientMaxFailureCount { get; set; } - - /// - /// Text representation of the Socket Status enum values for the server - /// - public string Status + public class GenericTcpIpServer : Device { - get + #region Events + /// + /// Event for Receiving text + /// + public event EventHandler TextReceived; + + /// + /// Event for client connection socket status change + /// + public event EventHandler ClientConnectionChange; + + /// + /// Event for Server State Change + /// + public event EventHandler ServerStateChange; + + /// + /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire + /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. + /// + public event EventHandler ServerClientReadyForCommunications; + + /// + /// A band aid event to notify user that the server has choked. + /// + public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } + + /// + /// Delegate for ServerHasChokedCallbackDelegate + /// + public delegate void ServerHasChokedCallbackDelegate(); + + #endregion + + #region Properties/Variables + + /// + /// + /// + CCriticalSection ServerCCSection = new CCriticalSection(); + + + /// + /// A bandaid client that monitors whether the server is reachable + /// + GenericTcpIpClient_ForServer MonitorClient; + + /// + /// Timer to operate the bandaid monitor client in a loop. + /// + CTimer MonitorClientTimer; + + /// + /// + /// + int MonitorClientFailureCount; + + /// + /// Gets or sets the MonitorClientMaxFailureCount + /// + public int MonitorClientMaxFailureCount { get; set; } + + /// + /// Text representation of the Socket Status enum values for the server + /// + public string Status { - if (myTcpServer != null) - return myTcpServer.State.ToString(); - return ServerState.SERVER_NOT_LISTENING.ToString(); + get + { + if (myTcpServer != null) + return myTcpServer.State.ToString(); + return ServerState.SERVER_NOT_LISTENING.ToString(); + + } } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get + /// + /// Bool showing if socket is connected + /// + public bool IsConnected { - if (myTcpServer != null) - return (myTcpServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; - return false; - - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : - // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsListening - { - get - { - if (myTcpServer != null) - return (myTcpServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; - else + get + { + if (myTcpServer != null) + return (myTcpServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; return false; - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : - // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); - } - } - /// - /// S+ helper for IsConnected - /// - public ushort UIsListening - { - get { return (ushort)(IsListening ? 1 : 0); } - } - - /// - /// The maximum number of clients. - /// Should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable - /// - public ushort MaxClients { get; set; } - - /// - /// Number of clients currently connected. - /// - public ushort NumberOfClientsConnected - { - get - { - if (myTcpServer != null) - return (ushort)myTcpServer.NumberOfClientsConnected; - return 0; - } - } - - /// - /// Port Server should listen on - /// - public int Port { get; set; } - - /// - /// S+ helper for Port - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. - /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called - /// - public string SharedKey { get; set; } - - /// - /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received - /// - public bool HeartbeatRequired { get; set; } - - /// - /// S+ Helper for Heartbeat Required - /// - public ushort UHeartbeatRequired - { - set - { - if (value == 1) - HeartbeatRequired = true; - else - HeartbeatRequired = false; - } - } - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatRequiredIntervalMs { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } - - /// - /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer - /// - public string HeartbeatStringToMatch { get; set; } - - //private timers for Heartbeats per client - Dictionary HeartbeatTimerDictionary = new Dictionary(); - - //flags to show the secure server is waiting for client at index to send the shared key - List WaitingForSharedKey = new List(); - - List ClientReadyAfterKeyExchange = new List(); - - /// - /// The connected client indexes - /// - public List ConnectedClientsIndexes = new List(); - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Private flag to note that the server has stopped intentionally - /// - private bool ServerStopped { get; set; } - - //Servers - TCPServer myTcpServer; - - /// - /// - /// - bool ProgramIsStopping; - - #endregion - - #region Constructors - /// - /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. - /// - public GenericTcpIpServer() - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - } - - /// - /// constructor with debug key set at instantiation. Make sure to set all properties before listening. - /// - /// - public GenericTcpIpServer(string key) - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Key = key; - } - - /// - /// Contstructor that sets all properties by calling the initialize method with a config object. - /// - /// - public GenericTcpIpServer(TcpServerConfigObject serverConfigObject) - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Initialize(serverConfigObject); - } - #endregion - - #region Methods - Server Actions - /// - /// Disconnects all clients and stops the server - /// - public void KillServer() - { - ServerStopped = true; - if (MonitorClient != null) - { - MonitorClient.Disconnect(); - } - DisconnectAllClientsForShutdown(); - StopListening(); - } - - /// - /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ - /// - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialze with server configuration object - /// - /// - public void Initialize(TcpServerConfigObject serverConfigObject) - { - try - { - if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) - { - Key = serverConfigObject.Key; - MaxClients = serverConfigObject.MaxClients; - Port = serverConfigObject.Port; - SharedKeyRequired = serverConfigObject.SharedKeyRequired; - SharedKey = serverConfigObject.SharedKey; - HeartbeatRequired = serverConfigObject.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; - HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; - BufferSize = serverConfigObject.BufferSize; - - } - else - { - this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key); + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : + // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); } } - catch - { - this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - /// - /// Start listening on the specified port - /// - public void Listen() - { - lock (_serverLock) + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected { - try + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is connected + /// + public bool IsListening { - if (Port < 1 || Port > 65535) + get { - this.LogError("Server '{0}': Invalid port", Key); - return; + if (myTcpServer != null) + return (myTcpServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; + else + return false; + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : + // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsListening + { + get { return (ushort)(IsListening ? 1 : 0); } + } + + /// + /// The maximum number of clients. + /// Should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable + /// + public ushort MaxClients { get; set; } + + /// + /// Number of clients currently connected. + /// + public ushort NumberOfClientsConnected + { + get { - this.LogError("Server '{0}': No Shared Key set", Key); - return; + if (myTcpServer != null) + return (ushort)myTcpServer.NumberOfClientsConnected; + return 0; } - if (IsListening) - return; + } - if (myTcpServer == null) + /// + /// Gets or sets the Port + /// + public int Port { get; set; } + + /// + /// S+ helper for Port + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set { - myTcpServer = new TCPServer(Port, MaxClients); - if(HeartbeatRequired) - myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); - - // myTcpServer.HandshakeTimeout = 30; + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; } - else + } + + /// + /// Gets or sets the SharedKey + /// + public string SharedKey { get; set; } + + /// + /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received + /// + public bool HeartbeatRequired { get; set; } + + /// + /// S+ Helper for Heartbeat Required + /// + public ushort UHeartbeatRequired + { + set { - KillServer(); - myTcpServer.PortNumber = Port; + if (value == 1) + HeartbeatRequired = true; + else + HeartbeatRequired = false; } - - myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; - myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange; - - ServerStopped = false; - myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - OnServerStateChange(myTcpServer.State); - this.LogInformation("TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus); - - // StartMonitorClient(); } - catch (Exception ex) - { - this.LogException(ex, "Error with Dynamic Server: {0}", ex.Message); - } - } // end lock - } - /// - /// Stop Listening - /// - public void StopListening() - { - try + /// + /// Gets or sets the HeartbeatRequiredIntervalMs + /// + public int HeartbeatRequiredIntervalMs { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } + + /// + /// Gets or sets the HeartbeatStringToMatch + /// + public string HeartbeatStringToMatch { get; set; } + + //private timers for Heartbeats per client + Dictionary HeartbeatTimerDictionary = new Dictionary(); + + //flags to show the secure server is waiting for client at index to send the shared key + List WaitingForSharedKey = new List(); + + List ClientReadyAfterKeyExchange = new List(); + + /// + /// The connected client indexes + /// + public List ConnectedClientsIndexes = new List(); + + /// + /// Gets or sets the BufferSize + /// + public int BufferSize { get; set; } + + /// + /// Private flag to note that the server has stopped intentionally + /// + private bool ServerStopped { get; set; } + + //Servers + TCPServer myTcpServer; + + /// + /// + /// + bool ProgramIsStopping; + + #endregion + + #region Constructors + /// + /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. + /// + public GenericTcpIpServer() + : base("Uninitialized Dynamic TCP Server") { - this.LogDebug("Stopping Listener"); - if (myTcpServer != null) + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + } + + /// + /// constructor with debug key set at instantiation. Make sure to set all properties before listening. + /// + /// + public GenericTcpIpServer(string key) + : base("Uninitialized Dynamic TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Key = key; + } + + /// + /// Contstructor that sets all properties by calling the initialize method with a config object. + /// + /// + public GenericTcpIpServer(TcpServerConfigObject serverConfigObject) + : base("Uninitialized Dynamic TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Initialize(serverConfigObject); + } + #endregion + + #region Methods - Server Actions + /// + /// KillServer method + /// + public void KillServer() + { + ServerStopped = true; + if (MonitorClient != null) { - myTcpServer.Stop(); - this.LogDebug("Server State: {0}", myTcpServer.State); - OnServerStateChange(myTcpServer.State); + MonitorClient.Disconnect(); } - ServerStopped = true; + DisconnectAllClientsForShutdown(); + StopListening(); } - catch (Exception ex) - { - this.LogException(ex, "Error stopping server. Error: {0}", ex.Message); - } - } - /// - /// Disconnects Client - /// - /// - public void DisconnectClient(uint client) - { - try + /// + /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ + /// + /// + /// + /// Initialize method + /// + public void Initialize(string key) { - myTcpServer.Disconnect(client); - this.LogVerbose("Disconnected client index: {0}", client); + Key = key; } - catch (Exception ex) + + /// + /// Initialze with server configuration object + /// + /// + public void Initialize(TcpServerConfigObject serverConfigObject) { - this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message); - } - } - /// - /// Disconnect All Clients - /// - public void DisconnectAllClientsForShutdown() - { - this.LogInformation("Disconnecting All Clients"); - if (myTcpServer != null) - { - myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; - foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + try { - var i = index; - if (!myTcpServer.ClientConnected(index)) - continue; - try + if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) { - myTcpServer.Disconnect(i); - this.LogVerbose("Disconnected client index: {0}", i); - } - catch (Exception ex) - { - this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message); - } - } - this.LogVerbose("Server Status: {0}", myTcpServer.ServerSocketStatus); - } + Key = serverConfigObject.Key; + MaxClients = serverConfigObject.MaxClients; + Port = serverConfigObject.Port; + SharedKeyRequired = serverConfigObject.SharedKeyRequired; + SharedKey = serverConfigObject.SharedKey; + HeartbeatRequired = serverConfigObject.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; + HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; + BufferSize = serverConfigObject.BufferSize; - this.LogInformation("Disconnected All Clients"); - ConnectedClientsIndexes.Clear(); - - if (!ProgramIsStopping) - { - OnConnectionChange(); - OnServerStateChange(myTcpServer.State); //State shows both listening and connected - } - - // var o = new { }; - } - - /// - /// Broadcast text from server to all connected clients - /// - /// - public void BroadcastText(string text) - { - lock (_broadcastLock) - { - try - { - if (ConnectedClientsIndexes.Count > 0) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - foreach (uint i in ConnectedClientsIndexes) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) - { - SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); - if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) - this.LogError("{error}",error.ToString()); - } - } - } - } - catch (Exception ex) - { - this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message); - } - } // end lock - } - - /// - /// Not sure this is useful in library, maybe Pro?? - /// - /// - /// - public void SendTextToClient(string text, uint clientIndex) - { - try - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (myTcpServer != null && myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - myTcpServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - } - } - catch (Exception ex) - { - this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); - } - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(uint clientIndex, string received) - { - try - { - if (HeartbeatRequired) - { - if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) - { - var remainingText = received.Replace(HeartbeatStringToMatch, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatStringToMatch)) - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; - HeartbeatTimerDictionary[clientIndex].Start(); - } - else - { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); - } - this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); - // Return Heartbeat - SendTextToClient(HeartbeatStringToMatch, clientIndex); - return remainingText; - } } else { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + catch + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + + /// + /// Listen method + /// + public void Listen() + { + ServerCCSection.Enter(); + try + { + if (Port < 1 || Port > 65535) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); + ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); + ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); + return; + } + if (IsListening) + return; + + if (myTcpServer == null) + { + myTcpServer = new TCPServer(Port, MaxClients); + if(HeartbeatRequired) + myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); + + // myTcpServer.HandshakeTimeout = 30; + } + else + { + KillServer(); + myTcpServer.PortNumber = Port; + } + + myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; + myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange; + + ServerStopped = false; + myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + OnServerStateChange(myTcpServer.State); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus); + + // StartMonitorClient(); + + + ServerCCSection.Leave(); + } + catch (Exception ex) + { + ServerCCSection.Leave(); + ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); + } + } + + /// + /// StopListening method + /// + public void StopListening() + { + try + { + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); + if (myTcpServer != null) + { + myTcpServer.Stop(); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State); + OnServerStateChange(myTcpServer.State); + } + ServerStopped = true; + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex); + } + } + + /// + /// Disconnects Client + /// + /// + /// + /// DisconnectClient method + /// + public void DisconnectClient(uint client) + { + try + { + myTcpServer.Disconnect(client); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); + } + } + /// + /// DisconnectAllClientsForShutdown method + /// + public void DisconnectAllClientsForShutdown() + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); + if (myTcpServer != null) + { + myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; + foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + { + var i = index; + if (!myTcpServer.ClientConnected(index)) + continue; + try { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs; - HeartbeatTimerDictionary[clientIndex].Start(); + myTcpServer.Disconnect(i); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); + } + } + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus); + } + + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); + ConnectedClientsIndexes.Clear(); + + if (!ProgramIsStopping) + { + OnConnectionChange(); + OnServerStateChange(myTcpServer.State); //State shows both listening and connected + } + + // var o = new { }; + } + + /// + /// Broadcast text from server to all connected clients + /// + /// + /// + /// BroadcastText method + /// + public void BroadcastText(string text) + { + CCriticalSection CCBroadcast = new CCriticalSection(); + CCBroadcast.Enter(); + try + { + if (ConnectedClientsIndexes.Count > 0) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + foreach (uint i in ConnectedClientsIndexes) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) + { + SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); + if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) + this.LogError("{error}",error.ToString()); + } + } + } + CCBroadcast.Leave(); + } + catch (Exception ex) + { + CCBroadcast.Leave(); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); + } + } + + /// + /// Not sure this is useful in library, maybe Pro?? + /// + /// + /// + /// + /// SendTextToClient method + /// + public void SendTextToClient(string text, uint clientIndex) + { + try + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + if (myTcpServer != null && myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + myTcpServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + } + } + catch (Exception ex) + { + Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); + } + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(uint clientIndex, string received) + { + try + { + if (HeartbeatRequired) + { + if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) + { + var remainingText = received.Replace(HeartbeatStringToMatch, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatStringToMatch)) + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); + // Return Heartbeat + SendTextToClient(HeartbeatStringToMatch, clientIndex); + return remainingText; + } } else { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); } - this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex); } } - } - catch (Exception ex) - { - this.LogException(ex, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - /// - /// Gets the IP address based on the client index - /// - /// - /// IP address of the client - public string GetClientIPAddress(uint clientIndex) - { - this.LogVerbose("GetClientIPAddress Index: {0}", clientIndex); - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - { - var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - this.LogVerbose("GetClientIPAddress IPAddreess: {0}", ipa); - return ipa; - - } - else - { - return ""; - } - } - - #endregion - - #region Methods - HeartbeatTimer Callback - - void HeartbeatTimer_CallbackFunction(object o) - { - uint clientIndex = 99999; - string address = string.Empty; - try - { - clientIndex = (uint)o; - address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - - this.LogWarning("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", - address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); - - if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); - - var discoResult = myTcpServer.Disconnect(clientIndex); - - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + catch (Exception ex) { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + /// + /// Gets the IP address based on the client index + /// + /// + /// IP address of the client + /// + /// GetClientIPAddress method + /// + public string GetClientIPAddress(uint clientIndex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + { + var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); + return ipa; + + } + else + { + return ""; } } - catch (Exception ex) + + #endregion + + #region Methods - HeartbeatTimer Callback + + void HeartbeatTimer_CallbackFunction(object o) { - this.LogException(ex, "Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message); - this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace); - } - } - - #endregion - - #region Methods - Socket Status Changed Callbacks - /// - /// Secure Server Socket Status Changed Callback - /// - /// - /// - /// - void TcpServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - try - { - - this.LogInformation("SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + uint clientIndex = 99999; + string address = string.Empty; + try { - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + clientIndex = (uint)o; + address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", + address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); + + if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); + + var discoResult = myTcpServer.Disconnect(clientIndex); + //Debug.Console(1, this, "{0}", discoResult); + + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) { HeartbeatTimerDictionary[clientIndex].Stop(); HeartbeatTimerDictionary[clientIndex].Dispose(); HeartbeatTimerDictionary.Remove(clientIndex); } - if (ClientReadyAfterKeyExchange.Contains(clientIndex)) - ClientReadyAfterKeyExchange.Remove(clientIndex); + } + catch (Exception ex) + { + ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); + } + } + + #endregion + + #region Methods - Socket Status Changed Callbacks + /// + /// Secure Server Socket Status Changed Callback + /// + /// + /// + /// + void TcpServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + try + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + if (ClientReadyAfterKeyExchange.Contains(clientIndex)) + ClientReadyAfterKeyExchange.Remove(clientIndex); if (WaitingForSharedKey.Contains(clientIndex)) WaitingForSharedKey.Remove(clientIndex); - } - } - catch (Exception ex) - { - this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex); - } - onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - } - - #endregion - - #region Methods Connected Callbacks - /// - /// Secure TCP Client Connected to Secure Server Callback - /// - /// - /// - void TcpConnectCallback(TCPServer server, uint clientIndex) - { - try - { - this.LogDebug("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), - clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - if (clientIndex != 0) - { - if (server.ClientConnected(clientIndex)) - { - - if (!ConnectedClientsIndexes.Contains(clientIndex)) - { - ConnectedClientsIndexes.Add(clientIndex); - } - if (SharedKeyRequired) - { - if (!WaitingForSharedKey.Contains(clientIndex)) - { - WaitingForSharedKey.Add(clientIndex); - } - byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); - server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - this.LogDebug("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - else - { - this.LogDebug("Client at index {0} is ready for communications", clientIndex); - OnServerClientReadyForCommunications(clientIndex); - } - if (HeartbeatRequired) - { - if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false }; - heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex); - heartbeatTimer.Start(); - HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer); - } - } - - server.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); } } - else + catch (Exception ex) { - this.LogError("Client attempt faulty."); - if (!ServerStopped) + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); + } + onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + } + + #endregion + + #region Methods Connected Callbacks + /// + /// Secure TCP Client Connected to Secure Server Callback + /// + /// + /// + void TcpConnectCallback(TCPServer server, uint clientIndex) + { + try + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), + clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + if (clientIndex != 0) { - server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - return; + if (server.ClientConnected(clientIndex)) + { + + if (!ConnectedClientsIndexes.Contains(clientIndex)) + { + ConnectedClientsIndexes.Add(clientIndex); + } + if (SharedKeyRequired) + { + if (!WaitingForSharedKey.Contains(clientIndex)) + { + WaitingForSharedKey.Add(clientIndex); + } + byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); + server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + else + { + OnServerClientReadyForCommunications(clientIndex); + } + if (HeartbeatRequired) + { + if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); + } + } + + server.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); + } + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); + if (!ServerStopped) + { + server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + return; + } } } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); + } + //Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))", + // server.State, + // MaxClients, + // ServerStopped); + if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection"); + server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + + } } - catch (Exception ex) + + #endregion + + #region Methods - Send/Receive Callbacks + /// + /// Secure Received Data Async Callback + /// + /// + /// + /// + void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) { - this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message); - this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace); - } - // server.State, - // MaxClients, - // ServerStopped); - if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) - { - this.LogDebug("Waiting for next connection"); - server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - - } - } - - #endregion - - #region Methods - Send/Receive Callbacks - /// - /// Secure Received Data Async Callback - /// - /// - /// - /// - void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) - { if (numberOfBytesReceived > 0) { string received = "Nothing"; @@ -819,7 +816,7 @@ public class GenericTcpIpServer : Device if (received != SharedKey) { byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting"); - this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); myTCPServer.SendData(clientIndex, b, b.Length); myTCPServer.Disconnect(clientIndex); return; @@ -829,7 +826,7 @@ public class GenericTcpIpServer : Device byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); myTCPServer.SendDataAsync(clientIndex, success, success.Length, null); OnServerClientReadyForCommunications(clientIndex); - this.LogDebug("Client with index {0} provided the shared key and successfully connected to the server", clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex); } else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) @@ -837,7 +834,7 @@ public class GenericTcpIpServer : Device } catch (Exception ex) { - this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex); } if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); @@ -848,182 +845,181 @@ public class GenericTcpIpServer : Device myTCPServer.Disconnect(); } - } + } - #endregion + #endregion - #region Methods - EventHelpers/Callbacks + #region Methods - EventHelpers/Callbacks - //Private Helper method to call the Connection Change Event - void onConnectionChange(uint clientIndex, SocketStatus clientStatus) - { - if (clientIndex != 0) //0 is error not valid client change + //Private Helper method to call the Connection Change Event + void onConnectionChange(uint clientIndex, SocketStatus clientStatus) { + if (clientIndex != 0) //0 is error not valid client change + { + var handler = ClientConnectionChange; + if (handler != null) + { + handler(this, new GenericTcpServerSocketStatusChangeEventArgs(myTcpServer, clientIndex, clientStatus)); + } + } + } + + //Private Helper method to call the Connection Change Event + void OnConnectionChange() + { + if (ProgramIsStopping) + { + return; + } var handler = ClientConnectionChange; if (handler != null) { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs(myTcpServer, clientIndex, clientStatus)); + handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); } } - } - //Private Helper method to call the Connection Change Event - void OnConnectionChange() - { - if (ProgramIsStopping) + //Private Helper Method to call the Text Received Event + void onTextReceived(string text, uint clientIndex) { - return; + var handler = TextReceived; + if (handler != null) + handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); } - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); - } - } - //Private Helper Method to call the Text Received Event - void onTextReceived(string text, uint clientIndex) - { - var handler = TextReceived; - if (handler != null) - handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); - } - - //Private Helper Method to call the Server State Change Event - void OnServerStateChange(ServerState state) - { - if (ProgramIsStopping) + //Private Helper Method to call the Server State Change Event + void OnServerStateChange(ServerState state) { - return; + if (ProgramIsStopping) + { + return; + } + var handler = ServerStateChange; + if (handler != null) + { + handler(this, new GenericTcpServerStateChangedEventArgs(state)); + } } - var handler = ServerStateChange; - if (handler != null) - { - handler(this, new GenericTcpServerStateChangedEventArgs(state)); - } - } - /// - /// Private Event Handler method to handle the closing of connections when the program stops - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) + /// + /// Private Event Handler method to handle the closing of connections when the program stops + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + ProgramIsStopping = true; + // kill bandaid things + if (MonitorClientTimer != null) + MonitorClientTimer.Stop(); + if (MonitorClient != null) + MonitorClient.Disconnect(); + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); + KillServer(); + } + } + + //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation + void OnServerClientReadyForCommunications(uint clientIndex) + { + ClientReadyAfterKeyExchange.Add(clientIndex); + var handler = ServerClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerSocketStatusChangeEventArgs( + this, clientIndex, myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex))); + } + #endregion + + #region Monitor Client + /// + /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient + /// + void StartMonitorClient() { - ProgramIsStopping = true; - // kill bandaid things if (MonitorClientTimer != null) + { + return; + } + MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); + } + + /// + /// + /// + void RunMonitorClient() + { + MonitorClient = new GenericTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); + MonitorClient.SharedKeyRequired = this.SharedKeyRequired; + MonitorClient.SharedKey = this.SharedKey; + MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; + //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; + MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); + + MonitorClient.Connect(); + // From here MonitorCLient either connects or hangs, MonitorClient will call back + + } + + /// + /// + /// + void StopMonitorClient() + { + if (MonitorClient == null) + return; + + MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; + MonitorClient.Disconnect(); + MonitorClient = null; + } + + /// + /// On monitor connect, restart the operation + /// + void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) + { + if (args.IsReady) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); MonitorClientTimer.Stop(); - if (MonitorClient != null) - MonitorClient.Disconnect(); - - this.LogInformation("Program stopping. Closing server"); - KillServer(); + MonitorClientTimer = null; + MonitorClientFailureCount = 0; + CrestronEnvironment.Sleep(2000); + StopMonitorClient(); + StartMonitorClient(); + } } - } - //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation - void OnServerClientReadyForCommunications(uint clientIndex) - { - ClientReadyAfterKeyExchange.Add(clientIndex); - var handler = ServerClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerSocketStatusChangeEventArgs( - this, clientIndex, myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex))); - } - #endregion - - #region Monitor Client - /// - /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient - /// - void StartMonitorClient() - { - if (MonitorClientTimer != null) + /// + /// If the client hangs, add to counter and maybe fire the choke event + /// + void MonitorClientHasHungCallback() { - return; - } - MonitorClientTimer = new Timer(60000) { AutoReset = false }; - MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient(); - MonitorClientTimer.Start(); - } - - /// - /// - /// - void RunMonitorClient() - { - MonitorClient = new GenericTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); - MonitorClient.SharedKeyRequired = this.SharedKeyRequired; - MonitorClient.SharedKey = this.SharedKey; - MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; - //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; - MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; - - this.LogDebug("Starting monitor check"); - - MonitorClient.Connect(); - // From here MonitorCLient either connects or hangs, MonitorClient will call back - - } - - /// - /// - /// - void StopMonitorClient() - { - if (MonitorClient == null) - return; - - MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; - MonitorClient.Disconnect(); - MonitorClient = null; - } - - /// - /// On monitor connect, restart the operation - /// - void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) - { - if (args.IsReady) - { - this.LogInformation("Monitor client connection success. Disconnecting in 2s"); + MonitorClientFailureCount++; MonitorClientTimer.Stop(); MonitorClientTimer = null; - MonitorClientFailureCount = 0; - CrestronEnvironment.Sleep(2000); StopMonitorClient(); - StartMonitorClient(); - } - } + if (MonitorClientFailureCount < MonitorClientMaxFailureCount) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}", + MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); + StartMonitorClient(); + } + else + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, + "\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************", + MonitorClientMaxFailureCount); - /// - /// If the client hangs, add to counter and maybe fire the choke event - /// - void MonitorClientHasHungCallback() - { - MonitorClientFailureCount++; - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - StopMonitorClient(); - if (MonitorClientFailureCount < MonitorClientMaxFailureCount) - { - this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}", - MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); - StartMonitorClient(); - } - else - { - this.LogError( - "\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************", - MonitorClientMaxFailureCount); - - var handler = ServerHasChoked; - if (handler != null) - handler(); - // Some external thing is in charge here. Expected reset of program + var handler = ServerHasChoked; + if (handler != null) + handler(); + // Some external thing is in charge here. Expected reset of program + } } + #endregion } - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/GenericUdpClient.cs b/src/PepperDash.Core/Comm/GenericUdpClient.cs new file mode 100644 index 00000000..41792537 --- /dev/null +++ b/src/PepperDash.Core/Comm/GenericUdpClient.cs @@ -0,0 +1,463 @@ +using System; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using ThreadingTimeout = System.Threading.Timeout; +using NetSocketException = System.Net.Sockets.SocketException; + +namespace PepperDash.Core +{ + /// + /// A class to handle basic UDP communications to a remote endpoint + /// + public class GenericUdpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect + { + private const string SplusKey = "Uninitialized UdpClient"; + + private readonly object stateLock = new object(); + private readonly Timer reconnectTimer; + + private UdpClient client; + private CancellationTokenSource receiveCancellationTokenSource; + private bool connectEnabled; + private bool connectionRefusedLogged; + private SocketStatus clientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Fires when data is received from the remote endpoint and returns it as a byte array + /// + public event EventHandler BytesReceived; + + /// + /// Fires when data is received from the remote endpoint and returns it as text + /// + public event EventHandler TextReceived; + + /// + /// Fires when the socket status changes + /// + public event EventHandler ConnectionChange; + + /// + /// Address of remote endpoint + /// + public string Hostname { get; set; } + + /// + /// Port on remote endpoint + /// + public int Port { get; set; } + + /// + /// Another S+ helper because large port numbers can be treated as signed ints + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// True when the local socket is created and associated with the configured remote endpoint + /// + public bool IsConnected + { + get { return ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// The current socket status of the client + /// + public SocketStatus ClientStatus + { + get + { + lock (stateLock) + { + return clientStatus; + } + } + private set + { + var shouldFireEvent = false; + + lock (stateLock) + { + if (clientStatus != value) + { + clientStatus = value; + shouldFireEvent = true; + } + } + + if (shouldFireEvent) + ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this)); + } + } + + /// + /// Ushort representation of client status + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Gets or sets the AutoReconnect + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Constructor + /// + public GenericUdpClient(string key, string address, int port, int bufferSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + AutoReconnectIntervalMs = 5000; + Hostname = address; + Port = port; + BufferSize = bufferSize; + + reconnectTimer = new Timer(o => + { + if (connectEnabled) + Connect(); + }, null, ThreadingTimeout.Infinite, ThreadingTimeout.Infinite); + } + + /// + /// Constructor for S+ + /// + public GenericUdpClient() + : base(SplusKey) + { + StreamDebugging = new CommunicationStreamDebugging(SplusKey); + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + reconnectTimer = new Timer(o => + { + if (connectEnabled) + Connect(); + }, null, ThreadingTimeout.Infinite, ThreadingTimeout.Infinite); + } + + /// + /// Initialize method + /// + public void Initialize(string key) + { + Key = key; + } + + private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + Debug.Console(1, this, "Program stopping. Closing connection"); + Deactivate(); + } + } + + /// + /// Deactivate method + /// + public override bool Deactivate() + { + Disconnect(); + return true; + } + + /// + /// Connect method + /// + public void Connect() + { + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpClient '{0}': No address set", Key); + ClientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + return; + } + + if (Port < 1 || Port > 65535) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpClient '{0}': Invalid port", Key); + ClientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + return; + } + + var hostname = Hostname; + var port = Port; + var bufferSize = BufferSize; + UdpClient newClient = null; + CancellationTokenSource newReceiveCancellationTokenSource = null; + CancellationToken startReceiveToken = default(CancellationToken); + var shouldStartReceive = false; + + lock (stateLock) + { + connectEnabled = true; + + if (client != null) + return; + } + + try + { + newReceiveCancellationTokenSource = new CancellationTokenSource(); + newClient = new UdpClient(); + newClient.Client.ReceiveBufferSize = bufferSize; + newClient.Client.SendBufferSize = bufferSize; + newClient.Connect(hostname, port); + + lock (stateLock) + { + if (!connectEnabled || client != null) + { + newClient.Close(); + newReceiveCancellationTokenSource.Cancel(); + newReceiveCancellationTokenSource.Dispose(); + return; + } + + receiveCancellationTokenSource = newReceiveCancellationTokenSource; + client = newClient; + ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; + reconnectTimer.Change(ThreadingTimeout.Infinite, ThreadingTimeout.Infinite); + startReceiveToken = receiveCancellationTokenSource.Token; + shouldStartReceive = true; + } + + if (shouldStartReceive) + StartReceive(startReceiveToken); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error connecting UDP client {0}", this, Key); + + if (newClient != null) + newClient.Close(); + + if (newReceiveCancellationTokenSource != null) + { + newReceiveCancellationTokenSource.Cancel(); + newReceiveCancellationTokenSource.Dispose(); + } + + lock (stateLock) + { + if (connectEnabled && client == null) + { + ClientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + StartReconnectTimer(); + } + } + } + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + lock (stateLock) + { + connectEnabled = false; + reconnectTimer.Change(ThreadingTimeout.Infinite, ThreadingTimeout.Infinite); + CleanupClient(); + ClientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + } + } + + /// + /// SendText method + /// + public void SendText(string text) + { + this.PrintSentText(text); + + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + SendBytes(bytes); + } + + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + if (bytes == null) + return; + + try + { + this.PrintSentBytes(bytes); + + if (!IsConnected || client == null) + Connect(); + + var udpClient = client; + if (!IsConnected || udpClient == null) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpClient '{0}': Cannot send bytes because the client is not connected", Key); + return; + } + + udpClient.Send(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error sending UDP bytes for {0}", this, Key); + HandleDisconnected(); + } + } + + private void StartReceive(CancellationToken token) + { + Task.Run(async () => + { + while (!token.IsCancellationRequested) + { + try + { + var udpClient = client; + if (udpClient == null) + return; + + var result = await udpClient.ReceiveAsync().ConfigureAwait(false); + var bytes = result.Buffer; + if (bytes == null || bytes.Length == 0) + continue; + + connectionRefusedLogged = false; + + var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + + this.PrintReceivedBytes(bytes); + this.PrintReceivedText(text); + + BytesReceived?.Invoke(this, new GenericCommMethodReceiveBytesArgs(bytes)); + TextReceived?.Invoke(this, new GenericCommMethodReceiveTextArgs(text)); + } + catch (ObjectDisposedException) + { + return; + } + catch (InvalidOperationException) + { + return; + } + catch (NetSocketException ex) + { + if (ex.SocketErrorCode == SocketError.ConnectionRefused) + { + if (!connectionRefusedLogged) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, + "GenericUdpClient '{0}': Remote endpoint refused UDP traffic or is no longer listening", + Key); + connectionRefusedLogged = true; + } + + HandleDisconnected(); + return; + } + + Debug.LogMessage(ex, "UDP receive error for {0}", this, Key); + + if (AutoReconnect) + { + HandleDisconnected(); + return; + } + + continue; + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Unexpected UDP receive error for {0}", this, Key); + + if (AutoReconnect) + { + HandleDisconnected(); + return; + } + + continue; + } + } + }, token); + } + + private void HandleDisconnected() + { + lock (stateLock) + { + CleanupClient(); + ClientStatus = SocketStatus.SOCKET_STATUS_NO_CONNECT; + StartReconnectTimer(); + } + } + + private void StartReconnectTimer() + { + if (AutoReconnect && connectEnabled) + reconnectTimer.Change(AutoReconnectIntervalMs, ThreadingTimeout.Infinite); + } + + private void CleanupClient() + { + if (receiveCancellationTokenSource != null) + { + receiveCancellationTokenSource.Cancel(); + receiveCancellationTokenSource.Dispose(); + receiveCancellationTokenSource = null; + } + + if (client != null) + { + client.Close(); + client = null; + } + } + } +} diff --git a/src/PepperDash.Core/Comm/GenericUdpServer.cs b/src/PepperDash.Core/Comm/GenericUdpServer.cs index 2d18d741..a713872a 100644 --- a/src/PepperDash.Core/Comm/GenericUdpServer.cs +++ b/src/PepperDash.Core/Comm/GenericUdpServer.cs @@ -1,357 +1,371 @@  -extern alias NewtonsoftJson; - using System; using System.Linq; using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; +using Newtonsoft.Json; using PepperDash.Core.Logging; -using Required = NewtonsoftJson::Newtonsoft.Json.Required; -namespace PepperDash.Core; - -/// -/// Generic UDP Server device -/// -public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging +namespace PepperDash.Core { - private const string SplusKey = "Uninitialized Udp Server"; /// - /// Object to enable stream debugging + /// Generic UDP Server device /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - /// - /// - /// - public event EventHandler BytesReceived; + public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging + { + private const string SplusKey = "Uninitialized Udp Server"; + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// + /// + public event EventHandler BytesReceived; - /// - /// - /// - public event EventHandler TextReceived; + /// + /// + /// + public event EventHandler TextReceived; - /// - /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data. - /// + /// + /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data. + /// public event EventHandler DataRecievedExtra; - /// - /// - /// - public event EventHandler ConnectionChange; + /// + /// + /// + public event EventHandler ConnectionChange; - /// - /// - /// - public event EventHandler UpdateConnectionStatus; + /// + /// + /// + public event EventHandler UpdateConnectionStatus; - /// - /// - /// - public SocketStatus ClientStatus - { - get + /// + /// + /// + public SocketStatus ClientStatus { - return Server.ServerStatus; + get + { + return Server.ServerStatus; + } } + + /// + /// + /// + public ushort UStatus + { + get { return (ushort)Server.ServerStatus; } + } + + /// + /// Address of server + /// + public string Hostname { get; set; } + + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// Another damn S+ helper because S+ seems to treat large port nums as signed ints + /// which screws up things + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Indicates that the UDP Server is enabled + /// + public bool IsConnected + { + get; + private set; + } + + /// + /// Numeric value indicating + /// + public ushort UIsConnected + { + get { return IsConnected ? (ushort)1 : (ushort)0; } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// The server + /// + public UDPServer Server { get; private set; } + + /// + /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method + /// + public GenericUdpServer() + : base(SplusKey) + { + StreamDebugging = new CommunicationStreamDebugging(SplusKey); + BufferSize = 5000; + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + } + + /// + /// + /// + /// + /// + /// + /// + public GenericUdpServer(string key, string address, int port, int bufferSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + Hostname = address; + Port = port; + BufferSize = bufferSize; + + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); + } + + /// + /// Call from S+ to initialize values + /// + /// + /// + /// + /// + /// Initialize method + /// + public void Initialize(string key, string address, ushort port) + { + Key = key; + Hostname = address; + UPort = port; + } + + /// + /// + /// + /// + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) + { + // Re-enable the server if the link comes back up and the status should be connected + if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp + && IsConnected) + { + Connect(); + } + } + + /// + /// + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType != eProgramStatusEventType.Stopping) + return; + + Debug.Console(1, this, "Program stopping. Disabling Server"); + Disconnect(); + } + + /// + /// Connect method + /// + public void Connect() + { + if (Server == null) + { + try + { + var address = IPAddress.Parse(Hostname); + + Server = new UDPServer(address, Port, BufferSize); + + } + catch (Exception ex) + { + this.LogError("Error parsing IP Address '{ipAddress}': message: {message}", Hostname, ex.Message); + this.LogInformation("Creating UDPServer with default buffersize"); + + Server = new UDPServer(); + } + + } + + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key); + return; + } + if (Port < 1 || Port > 65535) + { + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key); + return; + } + } + + var status = Server.EnableUDPServer(Hostname, Port); + + Debug.Console(2, this, "SocketErrorCode: {0}", status); + if (status == SocketErrorCodes.SOCKET_OK) + IsConnected = true; + + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); + + // Start receiving data + Server.ReceiveDataAsync(Receive); + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + if (Server != null) + Server.DisableUDPServer(); + + IsConnected = false; + + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); + } + + + /// + /// Recursive method to receive data + /// + /// + /// + void Receive(UDPServer server, int numBytes) + { + Debug.Console(2, this, "Received {0} bytes", numBytes); + + try + { + if (numBytes <= 0) + return; + + var sourceIp = Server.IPAddressLastMessageReceivedFrom; + var sourcePort = Server.IPPortLastMessageReceivedFrom; + var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray(); + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + + var dataRecivedExtra = DataRecievedExtra; + if (dataRecivedExtra != null) + dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes)); + + Debug.Console(2, this, "Bytes: {0}", bytes.ToString()); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + this.PrintReceivedBytes(bytes); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + this.PrintReceivedText(str); + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + catch (Exception ex) + { + this.LogException(ex, "GenericUdpServer Receive error"); + } + finally + { + server.ReceiveDataAsync(Receive); + } + } + + /// + /// General send method + /// + /// + /// + /// SendText method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + + if (IsConnected && Server != null) + { + this.PrintSentText(text); + + Server.SendData(bytes, bytes.Length); + } + } + + /// + /// + /// + /// + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + this.PrintSentBytes(bytes); + + if (IsConnected && Server != null) + Server.SendData(bytes, bytes.Length); + } + } /// /// Represents a GenericUdpReceiveTextExtraArgs /// - public ushort UStatus + public class GenericUdpReceiveTextExtraArgs : EventArgs { - get { return (ushort)Server.ServerStatus; } - } - - /// - /// Address of server - /// - public string Hostname { get; set; } - - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// Another damn S+ helper because S+ seems to treat large port nums as signed ints - /// which screws up things - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Indicates that the UDP Server is enabled - /// - public bool IsConnected - { - get; - private set; - } - - /// - /// Numeric value indicating - /// - public ushort UIsConnected - { - get { return IsConnected ? (ushort)1 : (ushort)0; } - } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// The server - /// - public UDPServer Server { get; private set; } - - /// - /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method - /// - public GenericUdpServer() - : base(SplusKey) - { - StreamDebugging = new CommunicationStreamDebugging(SplusKey); - BufferSize = 5000; - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); - } - - /// - /// - /// - /// - /// - /// - /// - public GenericUdpServer(string key, string address, int port, int buffefSize) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - Hostname = address; - Port = port; - BufferSize = buffefSize; - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); - } - - /// - /// Call from S+ to initialize values - /// - /// - /// - /// - public void Initialize(string key, string address, ushort port) - { - Key = key; - Hostname = address; - UPort = port; - } - - /// - /// - /// - /// - void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) - { - // Re-enable the server if the link comes back up and the status should be connected - if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp - && IsConnected) - { - Connect(); - } - } - - /// - /// - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType != eProgramStatusEventType.Stopping) - return; - - this.LogInformation("Program stopping. Disabling Server"); - Disconnect(); - } - - /// - /// Enables the UDP Server - /// - public void Connect() - { - if (Server == null) - { - Server = new UDPServer(); - } - - if (string.IsNullOrEmpty(Hostname)) - { - this.LogWarning("GenericUdpServer '{0}': No address set", Key); - return; - } - if (Port < 1 || Port > 65535) - { - { - this.LogWarning("GenericUdpServer '{0}': Invalid port", Key); - return; - } - } - - var status = Server.EnableUDPServer(Hostname, Port); - - this.LogVerbose("SocketErrorCode: {0}", status); - if (status == SocketErrorCodes.SOCKET_OK) - IsConnected = true; - - var handler = UpdateConnectionStatus; - if (handler != null) - handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); - - // Start receiving data - Server.ReceiveDataAsync(Receive); - } - - /// - /// Disabled the UDP Server - /// - public void Disconnect() - { - if(Server != null) - Server.DisableUDPServer(); - - IsConnected = false; - - var handler = UpdateConnectionStatus; - if (handler != null) - handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); - } - - - /// - /// Recursive method to receive data - /// - /// - /// - void Receive(UDPServer server, int numBytes) - { - this.LogVerbose("Received {0} bytes", numBytes); - - try - { - if (numBytes <= 0) - return; - - var sourceIp = Server.IPAddressLastMessageReceivedFrom; - var sourcePort = Server.IPPortLastMessageReceivedFrom; - var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray(); - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - - var dataRecivedExtra = DataRecievedExtra; - if (dataRecivedExtra != null) - dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes)); - - this.LogVerbose("Bytes: {0}", bytes.ToString()); - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - } - catch (Exception ex) - { - this.LogException(ex, "GenericUdpServer Receive error"); - } - finally - { - server.ReceiveDataAsync(Receive); - } - } - - /// - /// General send method - /// - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - - if (IsConnected && Server != null) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogVerbose("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); - - Server.SendData(bytes, bytes.Length); - } - } - - /// - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - - if (IsConnected && Server != null) - Server.SendData(bytes, bytes.Length); - } - -} - -/// -/// -/// - public class GenericUdpReceiveTextExtraArgs : EventArgs - { - /// - /// - /// + /// + /// + /// public string Text { get; private set; } - /// - /// - /// + /// + /// + /// public string IpAddress { get; private set; } - /// - /// - /// - public int Port { get; private set; } - /// - /// - /// + /// + /// + /// + public int Port { get; private set; } + /// + /// + /// public byte[] Bytes { get; private set; } - /// - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes) { Text = text; @@ -366,33 +380,34 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging public GenericUdpReceiveTextExtraArgs() { } } -/// -/// -/// -public class UdpServerPropertiesConfig -{ /// /// /// - [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - - /// - /// - /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } - - /// - /// - /// - public UdpServerPropertiesConfig() + public class UdpServerPropertiesConfig { - BufferSize = 32768; + /// + /// + /// + [JsonProperty(Required = Required.Always)] + public string Address { get; set; } + + /// + /// + /// + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } + + /// + /// + /// + public UdpServerPropertiesConfig() + { + BufferSize = 32768; + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/TcpClientConfigObject.cs b/src/PepperDash.Core/Comm/TcpClientConfigObject.cs index 2aec5af8..226473bf 100644 --- a/src/PepperDash.Core/Comm/TcpClientConfigObject.cs +++ b/src/PepperDash.Core/Comm/TcpClientConfigObject.cs @@ -1,60 +1,59 @@ -extern alias NewtonsoftJson; +using Newtonsoft.Json; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; - -namespace PepperDash.Core; - -/// -/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat -/// -public class TcpClientConfigObject +namespace PepperDash.Core { /// - /// TcpSsh Properties + /// Represents a TcpClientConfigObject /// - [JsonProperty("control")] - public ControlPropertiesConfig Control { get; set; } + public class TcpClientConfigObject + { + /// + /// TcpSsh Properties + /// + [JsonProperty("control")] + public ControlPropertiesConfig Control { get; set; } - /// - /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic - /// - [JsonProperty("secure")] - public bool Secure { get; set; } + /// + /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic + /// + [JsonProperty("secure")] + public bool Secure { get; set; } - /// - /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client - /// - [JsonProperty("sharedKeyRequired")] - public bool SharedKeyRequired { get; set; } + /// + /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client + /// + [JsonProperty("sharedKeyRequired")] + public bool SharedKeyRequired { get; set; } - /// - /// The shared key that must match on the server and client - /// - [JsonProperty("sharedKey")] - public string SharedKey { get; set; } + /// + /// The shared key that must match on the server and client + /// + [JsonProperty("sharedKey")] + public string SharedKey { get; set; } - /// - /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. - /// heartbeats do not raise received events. - /// - [JsonProperty("heartbeatRequired")] - public bool HeartbeatRequired { get; set; } + /// + /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. + /// heartbeats do not raise received events. + /// + [JsonProperty("heartbeatRequired")] + public bool HeartbeatRequired { get; set; } - /// - /// The interval in seconds for the heartbeat from the client. If not received client is disconnected - /// - [JsonProperty("heartbeatRequiredIntervalInSeconds")] - public ushort HeartbeatRequiredIntervalInSeconds { get; set; } + /// + /// The interval in seconds for the heartbeat from the client. If not received client is disconnected + /// + [JsonProperty("heartbeatRequiredIntervalInSeconds")] + public ushort HeartbeatRequiredIntervalInSeconds { get; set; } - /// - /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. - /// - [JsonProperty("heartbeatStringToMatch")] - public string HeartbeatStringToMatch { get; set; } + /// + /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. + /// + [JsonProperty("heartbeatStringToMatch")] + public string HeartbeatStringToMatch { get; set; } - /// - /// Receive Queue size must be greater than 20 or defaults to 20 - /// - [JsonProperty("receiveQueueSize")] - public int ReceiveQueueSize { get; set; } + /// + /// Receive Queue size must be greater than 20 or defaults to 20 + /// + [JsonProperty("receiveQueueSize")] + public int ReceiveQueueSize { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/TcpServerConfigObject.cs b/src/PepperDash.Core/Comm/TcpServerConfigObject.cs index b60f486f..043cf58d 100644 --- a/src/PepperDash.Core/Comm/TcpServerConfigObject.cs +++ b/src/PepperDash.Core/Comm/TcpServerConfigObject.cs @@ -4,56 +4,57 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core; - -/// -/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities -/// -public class TcpServerConfigObject +namespace PepperDash.Core { /// - /// Uique key + /// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities /// - public string Key { get; set; } - /// - /// Max Clients that the server will allow to connect. - /// - public ushort MaxClients { get; set; } - /// - /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic - /// - public bool Secure { get; set; } - /// - /// Port for the server to listen on - /// - public int Port { get; set; } - /// - /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client - /// - public bool SharedKeyRequired { get; set; } - /// - /// The shared key that must match on the server and client - /// - public string SharedKey { get; set; } - /// - /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. - /// heartbeats do not raise received events. - /// - public bool HeartbeatRequired { get; set; } - /// - /// The interval in seconds for the heartbeat from the client. If not received client is disconnected - /// - public ushort HeartbeatRequiredIntervalInSeconds { get; set; } - /// - /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. - /// - public string HeartbeatStringToMatch { get; set; } - /// - /// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000 - /// - public int BufferSize { get; set; } - /// - /// Receive Queue size must be greater than 20 or defaults to 20 - /// - public int ReceiveQueueSize { get; set; } + public class TcpServerConfigObject + { + /// + /// Uique key + /// + public string Key { get; set; } + /// + /// Max Clients that the server will allow to connect. + /// + public ushort MaxClients { get; set; } + /// + /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic + /// + public bool Secure { get; set; } + /// + /// Port for the server to listen on + /// + public int Port { get; set; } + /// + /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client + /// + public bool SharedKeyRequired { get; set; } + /// + /// The shared key that must match on the server and client + /// + public string SharedKey { get; set; } + /// + /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. + /// heartbeats do not raise received events. + /// + public bool HeartbeatRequired { get; set; } + /// + /// The interval in seconds for the heartbeat from the client. If not received client is disconnected + /// + public ushort HeartbeatRequiredIntervalInSeconds { get; set; } + /// + /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. + /// + public string HeartbeatStringToMatch { get; set; } + /// + /// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000 + /// + public int BufferSize { get; set; } + /// + /// Receive Queue size must be greater than 20 or defaults to 20 + /// + public int ReceiveQueueSize { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/eControlMethods.cs b/src/PepperDash.Core/Comm/eControlMethods.cs index db1bcb19..a1a30734 100644 --- a/src/PepperDash.Core/Comm/eControlMethods.cs +++ b/src/PepperDash.Core/Comm/eControlMethods.cs @@ -4,83 +4,89 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core; - -/// -/// Crestron Control Methods for a comm object -/// -public enum eControlMethod +namespace PepperDash.Core { /// - /// + /// Crestron Control Methods for a comm object /// - None = 0, - /// - /// RS232/422/485 - /// - Com, - /// - /// Crestron IpId (most Crestron ethernet devices) - /// - IpId, - /// - /// Crestron IpIdTcp (HD-MD series, etc.) - /// - IpidTcp, - /// - /// Crestron IR control - /// - IR, - /// - /// SSH client - /// - Ssh, - /// - /// TCP/IP client - /// - Tcpip, - /// - /// Telnet - /// - Telnet, - /// - /// Crestnet device - /// - Cresnet, - /// - /// CEC Control, via a DM HDMI port - /// - Cec, - /// - /// UDP Server - /// - Udp, - /// - /// HTTP client - /// - Http, - /// - /// HTTPS client - /// - Https, - /// - /// Websocket client - /// - Ws, - /// - /// Secure Websocket client - /// - Wss, - /// - /// Secure TCP/IP - /// - SecureTcpIp, - /// - /// Crestron COM bridge - /// - ComBridge, - /// - /// Crestron Infinet EX device - /// - InfinetEx + public enum eControlMethod + { + /// + /// + /// + None = 0, + /// + /// RS232/422/485 + /// + Com = 1, + /// + /// Crestron IpId (most Crestron ethernet devices) + /// + IpId = 2, + /// + /// Crestron IpIdTcp (HD-MD series, etc.) + /// + IpidTcp = 3, + /// + /// Crestron IR control + /// + IR = 4, + /// + /// SSH client + /// + Ssh = 5, + /// + /// TCP/IP client + /// + Tcpip = 6, + /// + /// Telnet + /// + Telnet = 7, + /// + /// Crestnet device + /// + Cresnet = 8, + /// + /// CEC Control, via a DM HDMI port + /// + Cec = 9, + /// + /// UDP Server + /// + Udp = 10, + + /// + /// HTTP client + /// + Http = 11, + /// + /// HTTPS client + /// + Https = 12, + /// + /// Websocket client + /// + Ws = 13, + /// + /// Secure Websocket client + /// + Wss = 14, + /// + /// Secure TCP/IP + /// + SecureTcpIp = 15, + /// + /// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction + /// + ComBridge = 16, + /// + /// InfinetEX control + /// + InfinetEx = 17, + /// + /// UDP client + /// + UdpClient = 18, + } } \ No newline at end of file diff --git a/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs b/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs new file mode 100644 index 00000000..f9f7eb3f --- /dev/null +++ b/src/PepperDash.Core/Comm/eStreamDebuggingSetting.cs @@ -0,0 +1,28 @@ +using System; + +namespace PepperDash.Core +{ + /// + /// The available settings for stream debugging + /// + [Flags] + public enum eStreamDebuggingSetting + { + /// + /// Debug off + /// + Off = 0, + /// + /// Debug received data + /// + Rx = 1, + /// + /// Debug transmitted data + /// + Tx = 2, + /// + /// Debug both received and transmitted data + /// + Both = Rx | Tx + } +} diff --git a/src/PepperDash.Core/CommunicationExtras.cs b/src/PepperDash.Core/CommunicationExtras.cs index 04e36982..a8bc57d3 100644 --- a/src/PepperDash.Core/CommunicationExtras.cs +++ b/src/PepperDash.Core/CommunicationExtras.cs @@ -1,143 +1,136 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; -using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter; +using Newtonsoft.Json; -namespace PepperDash.Core; - -/// -/// An incoming communication stream -/// -public interface ICommunicationReceiver : IKeyed +namespace PepperDash.Core { /// - /// Notifies of bytes received + /// An incoming communication stream /// - event EventHandler BytesReceived; - /// - /// Notifies of text received - /// - event EventHandler TextReceived; + public interface ICommunicationReceiver : IKeyed + { + /// + /// Notifies of bytes received + /// + event EventHandler BytesReceived; + /// + /// Notifies of text received + /// + event EventHandler TextReceived; + + /// + /// Indicates connection status + /// + [JsonProperty("isConnected")] + bool IsConnected { get; } + /// + /// Connect to the device + /// + void Connect(); + /// + /// Disconnect from the device + /// + void Disconnect(); + } /// - /// Indicates connection status + /// Defines the contract for IBasicCommunication /// - [JsonProperty("isConnected")] - bool IsConnected { get; } - /// - /// Connect to the device - /// - void Connect(); - /// - /// Disconnect from the device - /// - void Disconnect(); -} - - /// - /// Extends with methods for sending text and bytes to a device. - /// -public interface IBasicCommunication : ICommunicationReceiver - { - /// - /// Send text to the device - /// - /// + public interface IBasicCommunication : ICommunicationReceiver + { + /// + /// Send text to the device + /// + /// void SendText(string text); - /// - /// Send bytes to the device - /// - /// + /// + /// Send bytes to the device + /// + /// void SendBytes(byte[] bytes); } -/// -/// Represents a device that implements IBasicCommunication and IStreamDebugging -/// -public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging -{ - -} - -/// -/// Represents a device with stream debugging capablities -/// -public interface IStreamDebugging : IKeyed -{ /// - /// Object to enable stream debugging + /// Represents a device that implements IBasicCommunication and IStreamDebugging /// - [JsonProperty("streamDebugging")] - CommunicationStreamDebugging StreamDebugging { get; } -} + public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging + { + + } - /// - /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, - /// GenericTcpIpClient - /// - public interface ISocketStatus : IBasicCommunication - { /// - /// Notifies of socket status changes + /// Represents a device with stream debugging capablities /// + public interface IStreamDebugging : IKeyed + { + /// + /// Object to enable stream debugging + /// + [JsonProperty("streamDebugging")] + CommunicationStreamDebugging StreamDebugging { get; } + } + + /// + /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, + /// GenericTcpIpClient + /// + public interface ISocketStatus : IBasicCommunication + { + /// + /// Notifies of socket status changes + /// event EventHandler ConnectionChange; + /// + /// The current socket status of the client + /// + [JsonProperty("clientStatus")] + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + SocketStatus ClientStatus { get; } + } + /// - /// The current socket status of the client + /// Describes a device that implements ISocketStatus and IStreamDebugging /// - [JsonProperty("clientStatus")] - [JsonConverter(typeof(StringEnumConverter))] - SocketStatus ClientStatus { get; } - } + public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging + { -/// -/// Describes a device that implements ISocketStatus and IStreamDebugging -/// -public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging -{ + } -} - -/// -/// Describes a device that can automatically attempt to reconnect -/// + /// + /// Describes a device that can automatically attempt to reconnect + /// public interface IAutoReconnect - { - /// - /// Enable automatic recconnect - /// - [JsonProperty("autoReconnect")] - bool AutoReconnect { get; set; } - /// - /// Interval in ms to attempt automatic recconnections - /// - [JsonProperty("autoReconnectIntervalMs")] - int AutoReconnectIntervalMs { get; set; } - } + { + /// + /// Enable automatic recconnect + /// + [JsonProperty("autoReconnect")] + bool AutoReconnect { get; set; } + /// + /// Interval in ms to attempt automatic recconnections + /// + [JsonProperty("autoReconnectIntervalMs")] + int AutoReconnectIntervalMs { get; set; } + } - /// - /// - /// - public enum eGenericCommMethodStatusChangeType - { /// - /// Connected + /// /// + public enum eGenericCommMethodStatusChangeType + { + /// + /// Connected + /// Connected, - /// - /// Disconnected - /// - Disconnected - } + /// + /// Disconnected + /// + Disconnected + } /// /// This delegate defines handler for IBasicCommunication status changes @@ -146,20 +139,20 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg /// public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); - /// - /// Event args for bytes received from a communication method - /// - public class GenericCommMethodReceiveBytesArgs : EventArgs - { /// - /// The bytes received + /// /// - public byte[] Bytes { get; private set; } + public class GenericCommMethodReceiveBytesArgs : EventArgs + { + /// + /// Gets or sets the Bytes + /// + public byte[] Bytes { get; private set; } - /// - /// Constructor - /// - /// + /// + /// + /// + /// public GenericCommMethodReceiveBytesArgs(byte[] bytes) { Bytes = bytes; @@ -171,81 +164,42 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg public GenericCommMethodReceiveBytesArgs() { } } - /// - /// Event args for text received - /// - public class GenericCommMethodReceiveTextArgs : EventArgs - { /// - /// The text received + /// /// + public class GenericCommMethodReceiveTextArgs : EventArgs + { + /// + /// + /// public string Text { get; private set; } - /// - /// The delimiter used to determine the end of a message, if applicable - /// - public string Delimiter { get; private set; } - - /// - /// Constructor - /// - /// + /// + /// + /// + public string Delimiter { get; private set; } + /// + /// + /// + /// public GenericCommMethodReceiveTextArgs(string text) { Text = text; } - /// - /// - /// - /// - /// - public GenericCommMethodReceiveTextArgs(string text, string delimiter) - :this(text) - { - Delimiter = delimiter; + /// + /// + /// + /// + /// + public GenericCommMethodReceiveTextArgs(string text, string delimiter) + : this(text) + { + Delimiter = delimiter; + } + + /// + /// S+ Constructor + /// + public GenericCommMethodReceiveTextArgs() { } } - - /// - /// S+ Constructor - /// - public GenericCommMethodReceiveTextArgs() { } - } - - - - /// - /// Helper class to get escaped text for debugging communication streams - /// - public class ComTextHelper - { - /// - /// Gets escaped text for a byte array - /// - /// - /// - public static string GetEscapedText(byte[] bytes) - { - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - /// - /// Gets escaped text for a string - /// - /// - /// - public static string GetEscapedText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - /// - /// Gets debug text for a string - /// - /// - /// - public static string GetDebugText(string text) - { - return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); - } - } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Config/PortalConfigReader.cs b/src/PepperDash.Core/Config/PortalConfigReader.cs index 1e24658c..41ca2cbd 100644 --- a/src/PepperDash.Core/Config/PortalConfigReader.cs +++ b/src/PepperDash.Core/Config/PortalConfigReader.cs @@ -1,21 +1,19 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Linq; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting; -using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using JToken = NewtonsoftJson::Newtonsoft.Json.Linq.JToken; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Core.Config; +namespace PepperDash.Core.Config +{ -/// -/// Reads a Portal formatted config file -/// + + /// + /// Reads a Portal formatted config file + /// public class PortalConfigReader { const string template = "template"; @@ -58,12 +56,12 @@ namespace PepperDash.Core.Config; var merged = MergeConfigs(jsonObj); if (jsonObj[systemUrl] != null) { - merged[systemUrl] = (string)jsonObj[systemUrl]; + merged[systemUrl] = jsonObj[systemUrl].Value(); } if (jsonObj[templateUrl] != null) { - merged[templateUrl] = (string)jsonObj[templateUrl]; + merged[templateUrl] = jsonObj[templateUrl].Value(); } jsonObj = merged; @@ -124,37 +122,38 @@ namespace PepperDash.Core.Config; Merge(template[destinationLists], system[destinationLists], destinationLists)); - if (system["cameraLists"] == null) - merged.Add("cameraLists", template["cameraLists"]); - else - merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists")); + if (system[cameraLists] == null) + merged.Add(cameraLists, template[cameraLists]); + else + merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists)); - if (system["audioControlPointLists"] == null) - merged.Add("audioControlPointLists", template["audioControlPointLists"]); - else - merged.Add("audioControlPointLists", - Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists")); + if (system[audioControlPointLists] == null) + merged.Add(audioControlPointLists, template[audioControlPointLists]); + else + merged.Add(audioControlPointLists, + Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists)); - // Template tie lines take precedence. Config tool doesn't do them at system - // level anyway... - if (template["tieLines"] != null) - merged.Add("tieLines", template["tieLines"]); - else if (system["tieLines"] != null) - merged.Add("tieLines", system["tieLines"]); + // Template tie lines take precedence. Config tool doesn't do them at system + // level anyway... + if (template[tieLines] != null) + merged.Add(tieLines, template[tieLines]); + else if (system[tieLines] != null) + merged.Add(tieLines, system[tieLines]); else merged.Add(tieLines, new JArray()); - if (template["joinMaps"] != null) - merged.Add("joinMaps", template["joinMaps"]); - else - merged.Add("joinMaps", new JObject()); + if (template[joinMaps] != null) + merged.Add(joinMaps, template[joinMaps]); + else + merged.Add(joinMaps, new JObject()); if (system[global] != null) merged.Add(global, Merge(template[global], system[global], global)); else merged.Add(global, template[global]); + //Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged); return merged; } @@ -170,26 +169,26 @@ namespace PepperDash.Core.Config; return a1; else if (a1 != null) { - if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array - { // with the system array - return a2; - } - else // The arrays are keyed, merge them by key - { - for (int i = 0; i < a1.Count(); i++) - { - var a1Dev = a1[i]; - // Try to get a system device and if found, merge it onto template - var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value("uid") == tmplDev.Value("uid")); - if (a2Match != null) - { - var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match)); - result.Add(mergedItem); - } - else - result.Add(a1Dev); + if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array + { // with the system array + return a2; + } + else // The arrays are keyed, merge them by key + { + for (int i = 0; i < a1.Count(); i++) + { + var a1Dev = a1[i]; + // Try to get a system device and if found, merge it onto template + var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value("uid") == tmplDev.Value("uid")); + if (a2Match != null) + { + var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match)); + result.Add(mergedItem); + } + else + result.Add(a1Dev); + } } - } } return result; } @@ -206,9 +205,9 @@ namespace PepperDash.Core.Config; /// /// Merge o2 onto o1 /// - /// - /// - /// + /// + /// + /// static JObject Merge(JObject o1, JObject o2, string path) { foreach (var o2Prop in o2) @@ -254,4 +253,5 @@ namespace PepperDash.Core.Config; } return o1; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Conversion/Convert.cs b/src/PepperDash.Core/Conversion/Convert.cs index 13ef7263..49f8bb94 100644 --- a/src/PepperDash.Core/Conversion/Convert.cs +++ b/src/PepperDash.Core/Conversion/Convert.cs @@ -4,18 +4,28 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core; - -public class EncodingHelper +namespace PepperDash.Core { - public static string ConvertUtf8ToAscii(string utf8String) + /// + /// Represents a EncodingHelper + /// + public class EncodingHelper { - return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length); - } + /// + /// ConvertUtf8ToAscii method + /// + public static string ConvertUtf8ToAscii(string utf8String) + { + return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length); + } - public static string ConvertUtf8ToUtf16(string utf8String) - { - return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length); - } + /// + /// ConvertUtf8ToUtf16 method + /// + public static string ConvertUtf8ToUtf16(string utf8String) + { + return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length); + } + } } \ No newline at end of file diff --git a/src/PepperDash.Core/CoreInterfaces.cs b/src/PepperDash.Core/CoreInterfaces.cs index 89e6cdad..6e0b639e 100644 --- a/src/PepperDash.Core/CoreInterfaces.cs +++ b/src/PepperDash.Core/CoreInterfaces.cs @@ -1,35 +1,35 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; +using Newtonsoft.Json; using Serilog; -namespace PepperDash.Core; - -/// -/// Unique key interface to require a unique key for the class -/// +namespace PepperDash.Core +{ + /// + /// Unique key interface to require a unique key for the class + /// public interface IKeyed { - /// - /// Gets the unique key associated with the object. - /// - [JsonProperty("key")] + /// + /// Unique Key + /// + [JsonProperty("key")] string Key { get; } -} + } -/// -/// Named Keyed device interface. Forces the device to have a Unique Key and a name. -/// - public interface IKeyName : IKeyed -{ - /// - /// Gets the name associated with the current object. + /// + /// Named Keyed device interface. Forces the device to have a Unique Key and a name. /// - [JsonProperty("name")] + public interface IKeyName : IKeyed + { + /// + /// Isn't it obvious :) + /// + [JsonProperty("name")] string Name { get; } + } + } \ No newline at end of file diff --git a/src/PepperDash.Core/Device.cs b/src/PepperDash.Core/Device.cs index 709c712e..58b5fe3e 100644 --- a/src/PepperDash.Core/Device.cs +++ b/src/PepperDash.Core/Device.cs @@ -2,183 +2,188 @@ using System.Collections.Generic; using Serilog.Events; -namespace PepperDash.Core; - -//********************************************************************************************************* -/// -/// Represents a Device -/// -public class Device : IKeyName +namespace PepperDash.Core { - + //********************************************************************************************************* /// - /// Unique Key + /// Represents a Device /// - public string Key { get; protected set; } - /// - /// Gets or sets the Name - /// - public string Name { get; protected set; } - /// - /// - /// - public bool Enabled { get; protected set; } - - List _PreActivationActions; - List _PostActivationActions; - - /// - /// - /// - public static Device DefaultDevice { get { return _DefaultDevice; } } - static Device _DefaultDevice = new Device("Default", "Default"); - - /// - /// Base constructor for all Devices. - /// - /// - public Device(string key) + public class Device : IKeyName { - Key = key; - if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this); - Name = ""; - } - /// - /// Constructor with key and name - /// - /// - /// - public Device(string key, string name) : this(key) - { - Name = name; - } + /// + /// Unique Key + /// + public string Key { get; protected set; } + /// + /// Gets or sets the Name + /// + public string Name { get; protected set; } + /// + /// + /// + public bool Enabled { get; protected set; } - //public Device(DeviceConfig config) - // : this(config.Key, config.Name) - //{ - // Config = config; - //} + List _PreActivationActions; + List _PostActivationActions; - /// - /// Adds a pre activation action - /// - /// - protected void AddPreActivationAction(Action act) - { - if (_PreActivationActions == null) - _PreActivationActions = new List(); - _PreActivationActions.Add(act); - } + /// + /// + /// + public static Device DefaultDevice { get { return _DefaultDevice; } } + static Device _DefaultDevice = new Device("Default", "Default"); - /// - /// Adds a post activation action - /// - /// - /// - /// AddPostActivationAction method - /// - protected void AddPostActivationAction(Action act) - { - if (_PostActivationActions == null) - _PostActivationActions = new List(); - _PostActivationActions.Add(act); - } + /// + /// Base constructor for all Devices. + /// + /// + public Device(string key) + { + Key = key; + if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this); + Name = ""; + } - /// - /// PreActivate method - /// - public void PreActivate() - { - if (_PreActivationActions != null) - _PreActivationActions.ForEach(a => - { - try + /// + /// Constructor with key and name + /// + /// + /// + public Device(string key, string name) : this(key) + { + Name = name; + + } + + //public Device(DeviceConfig config) + // : this(config.Key, config.Name) + //{ + // Config = config; + //} + + /// + /// Adds a pre activation action + /// + /// + public void AddPreActivationAction(Action act) + { + if (_PreActivationActions == null) + _PreActivationActions = new List(); + _PreActivationActions.Add(act); + } + + /// + /// Adds a post activation action + /// + /// + /// + /// AddPostActivationAction method + /// + public void AddPostActivationAction(Action act) + { + if (_PostActivationActions == null) + _PostActivationActions = new List(); + _PostActivationActions.Add(act); + } + + /// + /// PreActivate method + /// + public void PreActivate() + { + if (_PreActivationActions != null) + _PreActivationActions.ForEach(a => { - a.Invoke(); - } - catch (Exception e) + try + { + a.Invoke(); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this); + } + }); + } + + /// + /// Activate method + /// + public bool Activate() + { + //if (_PreActivationActions != null) + // _PreActivationActions.ForEach(a => a.Invoke()); + var result = CustomActivate(); + //if(result && _PostActivationActions != null) + // _PostActivationActions.ForEach(a => a.Invoke()); + return result; + } + + /// + /// PostActivate method + /// + public void PostActivate() + { + if (_PostActivationActions != null) + _PostActivationActions.ForEach(a => { - Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this); - } - }); - } + try + { + a.Invoke(); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this); + } + }); + } - /// - /// Activate method - /// - public bool Activate() - { - //if (_PreActivationActions != null) - // _PreActivationActions.ForEach(a => a.Invoke()); - var result = CustomActivate(); - //if(result && _PostActivationActions != null) - // _PostActivationActions.ForEach(a => a.Invoke()); - return result; - } + /// + /// Called in between Pre and PostActivationActions when Activate() is called. + /// Override to provide addtitional setup when calling activation. Overriding classes + /// do not need to call base.CustomActivate() + /// + /// true if device activated successfully. + /// + /// CustomActivate method + /// + public virtual bool CustomActivate() { return true; } - /// - /// PostActivate method - /// - public void PostActivate() - { - if (_PostActivationActions != null) - _PostActivationActions.ForEach(a => - { - try - { - a.Invoke(); - } - catch (Exception e) - { - Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this); - } - }); - } + /// + /// Call to deactivate device - unlink events, etc. Overriding classes do not + /// need to call base.Deactivate() + /// + /// + public virtual bool Deactivate() { return true; } - /// - /// Called in between Pre and PostActivationActions when Activate() is called. - /// Override to provide addtitional setup when calling activation. Overriding classes - /// do not need to call base.CustomActivate() - /// - /// true if device activated successfully. - /// - /// CustomActivate method - /// - protected virtual bool CustomActivate() { return true; } + /// + /// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() + /// + public virtual void Initialize() + { + } - /// - /// Call to deactivate device - unlink events, etc. Overriding classes do not - /// need to call base.Deactivate() - /// - /// - public virtual bool Deactivate() { return true; } + /// + /// Helper method to check object for bool value false and fire an Action method + /// + /// Should be of type bool, others will be ignored + /// Action to be run when o is false + public void OnFalse(object o, Action a) + { + if (o is bool && !(bool)o) a(); + } - /// - /// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() - /// - protected virtual void Initialize() - { - } - - /// - /// Helper method to check object for bool value false and fire an Action method - /// - /// Should be of type bool, others will be ignored - /// Action to be run when o is false - public void OnFalse(object o, Action a) - { - if (o is bool && !(bool)o) a(); - } - - /// - /// Returns a string representation of the object, including its key and name. - /// - /// The returned string is formatted as "{Key} - {Name}". If the Name property is - /// null or empty, "---" is used in place of the name. - /// A string that represents the object, containing the key and name in the format "{Key} - {Name}". - public override string ToString() - { - return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name); + /// + /// Returns a string representation of the object, including its key and name. + /// + /// The returned string is formatted as "{Key} - {Name}". If the Name property is + /// null or empty, "---" is used in place of the name. + /// A string that represents the object, containing the key and name in the format "{Key} - {Name}". + /// + /// ToString method + /// + public override string ToString() + { + return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name); + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/EthernetHelper.cs b/src/PepperDash.Core/EthernetHelper.cs index 51372603..76a9405c 100644 --- a/src/PepperDash.Core/EthernetHelper.cs +++ b/src/PepperDash.Core/EthernetHelper.cs @@ -1,14 +1,12 @@ -extern alias NewtonsoftJson; - -using Crestron.SimplSharp; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; +using Crestron.SimplSharp; +using Newtonsoft.Json; using Serilog.Events; -namespace PepperDash.Core; - -/// -/// Represents an EthernetHelper. -/// +namespace PepperDash.Core +{ + /// + /// Represents a EthernetHelper + /// public class EthernetHelper { /// @@ -115,4 +113,5 @@ namespace PepperDash.Core; CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0); } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/EventArgs.cs b/src/PepperDash.Core/EventArgs.cs index 68c2be53..c146a6f2 100644 --- a/src/PepperDash.Core/EventArgs.cs +++ b/src/PepperDash.Core/EventArgs.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core; - +namespace PepperDash.Core +{ /// /// Bool change event args /// @@ -168,4 +168,5 @@ namespace PepperDash.Core; Type = type; Index = index; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/GenericRESTfulCommunications/Constants.cs b/src/PepperDash.Core/GenericRESTfulCommunications/Constants.cs new file mode 100644 index 00000000..1b78c33f --- /dev/null +++ b/src/PepperDash.Core/GenericRESTfulCommunications/Constants.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.GenericRESTfulCommunications +{ + /// + /// Constants + /// + public class GenericRESTfulConstants + { + /// + /// Generic boolean change + /// + public const ushort BoolValueChange = 1; + /// + /// Generic Ushort change + /// + public const ushort UshrtValueChange = 101; + /// + /// Response Code Ushort change + /// + public const ushort ResponseCodeChange = 102; + /// + /// Generic String chagne + /// + public const ushort StringValueChange = 201; + /// + /// Response string change + /// + public const ushort ResponseStringChange = 202; + /// + /// Error string change + /// + public const ushort ErrorStringChange = 203; + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/GenericRESTfulCommunications/GenericRESTfulClient.cs b/src/PepperDash.Core/GenericRESTfulCommunications/GenericRESTfulClient.cs new file mode 100644 index 00000000..bd33e13c --- /dev/null +++ b/src/PepperDash.Core/GenericRESTfulCommunications/GenericRESTfulClient.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.Net.Http; +using Crestron.SimplSharp.Net.Https; + +namespace PepperDash.Core.GenericRESTfulCommunications +{ + /// + /// Generic RESTful communication class + /// + public class GenericRESTfulClient + { + /// + /// Boolean event handler + /// + public event EventHandler BoolChange; + /// + /// Ushort event handler + /// + public event EventHandler UshrtChange; + /// + /// String event handler + /// + public event EventHandler StringChange; + + /// + /// Constructor + /// + public GenericRESTfulClient() + { + + } + + /// + /// Generic RESTful submit request + /// + /// + /// + /// + /// + /// + /// + public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) + { + SubmitRequestHttps(url, port, requestType, contentType, username, password); + } + else if (url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)) + { + SubmitRequestHttp(url, port, requestType, contentType, username, password); + } + else + { + OnStringChange(string.Format("Invalid URL {0}", url), 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private HTTP submit request + /// + /// + /// + /// + /// + /// + /// + private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + try + { + HttpClient client = new HttpClient(); + HttpClientRequest request = new HttpClientRequest(); + HttpClientResponse response; + + client.KeepAlive = false; + + if(port >= 1 || port <= 65535) + client.Port = port; + else + client.Port = 80; + + var authorization = ""; + if (!string.IsNullOrEmpty(username)) + authorization = EncodeBase64(username, password); + + if (!string.IsNullOrEmpty(authorization)) + request.Header.SetHeaderValue("Authorization", authorization); + + if (!string.IsNullOrEmpty(contentType)) + request.Header.ContentType = contentType; + + request.Url.Parse(url); + request.RequestType = (Crestron.SimplSharp.Net.Http.RequestType)requestType; + + response = client.Dispatch(request); + + CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); + + if (!string.IsNullOrEmpty(response.ContentString.ToString())) + OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); + + if (response.Code > 0) + OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); + } + catch (Exception e) + { + //var msg = string.Format("SubmitRequestHttp({0}, {1}, {2}) failed:{3}", url, port, requestType, e.Message); + //CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + + CrestronConsole.PrintLine(e.Message); + OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private HTTPS submit request + /// + /// + /// + /// + /// + /// + /// + private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + try + { + HttpsClient client = new HttpsClient(); + HttpsClientRequest request = new HttpsClientRequest(); + HttpsClientResponse response; + + client.KeepAlive = false; + client.HostVerification = false; + client.PeerVerification = false; + + var authorization = ""; + if (!string.IsNullOrEmpty(username)) + authorization = EncodeBase64(username, password); + + if (!string.IsNullOrEmpty(authorization)) + request.Header.SetHeaderValue("Authorization", authorization); + + if (!string.IsNullOrEmpty(contentType)) + request.Header.ContentType = contentType; + + request.Url.Parse(url); + request.RequestType = (Crestron.SimplSharp.Net.Https.RequestType)requestType; + + response = client.Dispatch(request); + + CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); + + if(!string.IsNullOrEmpty(response.ContentString.ToString())) + OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); + + if(response.Code > 0) + OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); + + } + catch (Exception e) + { + //var msg = string.Format("SubmitRequestHttps({0}, {1}, {2}, {3}, {4}) failed:{5}", url, port, requestType, username, password, e.Message); + //CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + + CrestronConsole.PrintLine(e.Message); + OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private method to encode username and password to Base64 string + /// + /// + /// + /// authorization + private string EncodeBase64(string username, string password) + { + var authorization = ""; + + try + { + if (!string.IsNullOrEmpty(username)) + { + string base64String = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(string.Format("{0}:{1}", username, password))); + authorization = string.Format("Basic {0}", base64String); + } + } + catch (Exception e) + { + var msg = string.Format("EncodeBase64({0}, {1}) failed:\r{2}", username, password, e); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + return "" ; + } + + return authorization; + } + + /// + /// Protected method to handle boolean change events + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected mehtod to handle ushort change events + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected method to handle string change events + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonStandardObjects/EventArgs and Constants.cs b/src/PepperDash.Core/JsonStandardObjects/EventArgs and Constants.cs index 668bd481..110587ab 100644 --- a/src/PepperDash.Core/JsonStandardObjects/EventArgs and Constants.cs +++ b/src/PepperDash.Core/JsonStandardObjects/EventArgs and Constants.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.JsonStandardObjects; - +namespace PepperDash.Core.JsonStandardObjects +{ /// /// Constants for simpl modules /// @@ -73,4 +73,5 @@ namespace PepperDash.Core.JsonStandardObjects; Type = type; Index = index; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDevice.cs b/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDevice.cs index 621e99e0..2ad47e27 100644 --- a/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDevice.cs +++ b/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDevice.cs @@ -4,8 +4,8 @@ using Crestron.SimplSharp; using PepperDash.Core.JsonToSimpl; using Serilog.Events; -namespace PepperDash.Core.JsonStandardObjects; - +namespace PepperDash.Core.JsonStandardObjects +{ /// /// Device class /// @@ -182,4 +182,5 @@ namespace PepperDash.Core.JsonStandardObjects; } #endregion EventHandler Helpers - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs b/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs index 5d98ba5a..d754a029 100644 --- a/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs +++ b/src/PepperDash.Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.JsonStandardObjects; - +namespace PepperDash.Core.JsonStandardObjects +{ /* Convert JSON snippt to C#: http://json2csharp.com/# @@ -52,55 +52,55 @@ namespace PepperDash.Core.JsonStandardObjects; /// public class ComParamsConfig { - /// - /// - /// + /// + /// Gets or sets the baudRate + /// public int baudRate { get; set; } - /// - /// - /// + /// + /// + /// public int dataBits { get; set; } - /// - /// - /// + /// + /// + /// public int stopBits { get; set; } - /// - /// - /// + /// + /// + /// public string parity { get; set; } - /// - /// - /// + /// + /// + /// public string protocol { get; set; } - /// - /// - /// + /// + /// + /// public string hardwareHandshake { get; set; } - /// - /// - /// + /// + /// + /// public string softwareHandshake { get; set; } - /// - /// - /// + /// + /// + /// public int pacing { get; set; } // convert properties for simpl - /// - /// - /// + /// + /// Gets or sets the simplBaudRate + /// public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } } - /// - /// - /// + /// + /// Gets or sets the simplDataBits + /// public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } } - /// - /// - /// + /// + /// + /// public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } } - /// - /// - /// + /// + /// + /// public ushort simplPacing { get { return Convert.ToUInt16(pacing); } } /// @@ -117,43 +117,43 @@ namespace PepperDash.Core.JsonStandardObjects; /// public class TcpSshPropertiesConfig { - /// - /// - /// + /// + /// + /// public string address { get; set; } - /// - /// - /// + /// + /// + /// public int port { get; set; } - /// - /// - /// + /// + /// + /// public string username { get; set; } - /// - /// - /// + /// + /// + /// public string password { get; set; } - /// - /// - /// + /// + /// + /// public bool autoReconnect { get; set; } - /// - /// - /// + /// + /// + /// public int autoReconnectIntervalMs { get; set; } // convert properties for simpl - /// - /// - /// + /// + /// Gets or sets the simplPort + /// public ushort simplPort { get { return Convert.ToUInt16(port); } } - /// - /// - /// + /// + /// Gets or sets the simplAutoReconnect + /// public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } } - /// - /// - /// + /// + /// + /// public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } } /// @@ -170,31 +170,31 @@ namespace PepperDash.Core.JsonStandardObjects; /// public class ControlConfig { - /// - /// - /// + /// + /// + /// public string method { get; set; } - /// - /// - /// + /// + /// + /// public string controlPortDevKey { get; set; } - /// - /// - /// + /// + /// + /// public int controlPortNumber { get; set; } - /// - /// - /// + /// + /// + /// public ComParamsConfig comParams { get; set; } - /// - /// - /// + /// + /// + /// public TcpSshPropertiesConfig tcpSshProperties { get; set; } // convert properties for simpl - /// - /// - /// + /// + /// Gets or sets the simplControlPortNumber + /// public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } } /// @@ -212,27 +212,27 @@ namespace PepperDash.Core.JsonStandardObjects; /// public class PropertiesConfig { - /// - /// - /// + /// + /// + /// public int deviceId { get; set; } - /// - /// - /// + /// + /// + /// public bool enabled { get; set; } - /// - /// - /// + /// + /// + /// public ControlConfig control { get; set; } // convert properties for simpl - /// - /// - /// + /// + /// Gets or sets the simplDeviceId + /// public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } } - /// - /// - /// + /// + /// Gets or sets the simplEnabled + /// public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } } /// @@ -249,8 +249,9 @@ namespace PepperDash.Core.JsonStandardObjects; /// public class RootObject { - /// - /// The collection of devices - /// + /// + /// The collection of devices + /// public List devices { get; set; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/Constants.cs b/src/PepperDash.Core/JsonToSimpl/Constants.cs index a04c1966..ca638bbf 100644 --- a/src/PepperDash.Core/JsonToSimpl/Constants.cs +++ b/src/PepperDash.Core/JsonToSimpl/Constants.cs @@ -4,78 +4,78 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.JsonToSimpl; - +namespace PepperDash.Core.JsonToSimpl +{ /// /// Constants for Simpl modules /// public class JsonToSimplConstants { - /// - /// - /// + /// + /// + /// public const ushort BoolValueChange = 1; - /// - /// - /// + /// + /// + /// public const ushort JsonIsValidBoolChange = 2; - /// - /// Reports the if the device is 3-series compatible - /// - public const ushort ProgramCompatibility3SeriesChange = 3; + /// + /// Reports the if the device is 3-series compatible + /// + public const ushort ProgramCompatibility3SeriesChange = 3; - /// - /// Reports the if the device is 4-series compatible - /// - public const ushort ProgramCompatibility4SeriesChange = 4; + /// + /// Reports the if the device is 4-series compatible + /// + public const ushort ProgramCompatibility4SeriesChange = 4; - /// - /// Reports the device platform enum value - /// - public const ushort DevicePlatformValueChange = 5; + /// + /// Reports the device platform enum value + /// + public const ushort DevicePlatformValueChange = 5; - /// - /// - /// + /// + /// + /// public const ushort UshortValueChange = 101; - /// - /// - /// + /// + /// + /// public const ushort StringValueChange = 201; - /// - /// - /// + /// + /// + /// public const ushort FullPathToArrayChange = 202; - /// - /// - /// + /// + /// + /// public const ushort ActualFilePathChange = 203; - /// - /// - /// + /// + /// + /// public const ushort FilenameResolvedChange = 204; - /// - /// - /// + /// + /// + /// public const ushort FilePathResolvedChange = 205; - /// - /// Reports the root directory change - /// - public const ushort RootDirectoryChange = 206; + /// + /// Reports the root directory change + /// + public const ushort RootDirectoryChange = 206; - /// - /// Reports the room ID change - /// - public const ushort RoomIdChange = 207; + /// + /// Reports the room ID change + /// + public const ushort RoomIdChange = 207; - /// - /// Reports the room name change - /// - public const ushort RoomNameChange = 208; + /// + /// Reports the room name change + /// + public const ushort RoomNameChange = 208; } /// @@ -88,33 +88,33 @@ namespace PepperDash.Core.JsonToSimpl; /// public class SPlusValueWrapper { - /// - /// - /// + /// + /// Gets or sets the ValueType + /// public SPlusType ValueType { get; private set; } - /// - /// - /// + /// + /// + /// public ushort Index { get; private set; } - /// - /// - /// + /// + /// + /// public ushort BoolUShortValue { get; set; } - /// - /// - /// + /// + /// + /// public string StringValue { get; set; } - /// - /// - /// + /// + /// + /// public SPlusValueWrapper() {} - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// public SPlusValueWrapper(SPlusType type, ushort index) { ValueType = type; @@ -127,16 +127,17 @@ namespace PepperDash.Core.JsonToSimpl; /// public enum SPlusType { - /// - /// Digital - /// + /// + /// Digital + /// Digital, - /// - /// Analog - /// - Analog, - /// - /// String - /// - String - } \ No newline at end of file + /// + /// Analog + /// + Analog, + /// + /// String + /// + String + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/Global.cs b/src/PepperDash.Core/JsonToSimpl/Global.cs index 39633a61..8d71c5fb 100644 --- a/src/PepperDash.Core/JsonToSimpl/Global.cs +++ b/src/PepperDash.Core/JsonToSimpl/Global.cs @@ -7,11 +7,11 @@ using Serilog.Events; //using PepperDash.Core; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// The global class to manage all the instances of JsonToSimplMaster -/// +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// The global class to manage all the instances of JsonToSimplMaster + /// public class J2SGlobal { static List Masters = new List(); @@ -22,7 +22,10 @@ namespace PepperDash.Core.JsonToSimpl; /// master, this will fail /// /// New master to add - /// + /// + /// + /// AddMaster method + /// public static void AddMaster(JsonToSimplMaster master) { if (master == null) @@ -56,4 +59,5 @@ namespace PepperDash.Core.JsonToSimpl; { return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase)); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs index f49a4b77..268f6207 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs @@ -1,24 +1,22 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Linq; -using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray; +using Newtonsoft.Json.Linq; using Serilog.Events; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Used to interact with an array of values with the S+ modules -/// +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Represents a JsonToSimplArrayLookupChild + /// public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase { - /// - /// - /// + /// + /// + /// public string SearchPropertyName { get; set; } - /// - /// - /// + /// + /// + /// public string SearchPropertyValue { get; set; } int ArrayIndex; @@ -78,9 +76,10 @@ namespace PepperDash.Core.JsonToSimpl; PathSuffix == null ? "" : PathSuffix); } - /// - /// Process all values - /// + /// + /// ProcessAll method + /// + /// public override void ProcessAll() { if (FindInArray()) @@ -130,7 +129,7 @@ namespace PepperDash.Core.JsonToSimpl; var item = array.FirstOrDefault(o => { var prop = o[SearchPropertyName]; - return prop != null && ((string)prop) + return prop != null && prop.Value() .Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase); }); if (item == null) @@ -160,4 +159,5 @@ namespace PepperDash.Core.JsonToSimpl; return false; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplChildObjectBase.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplChildObjectBase.cs index 5e644483..68e4bf91 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplChildObjectBase.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplChildObjectBase.cs @@ -1,34 +1,31 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; using System.Linq; -using PepperDash.Core.Logging; -using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; +using Newtonsoft.Json.Linq; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Base class for JSON objects -/// +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Base class for JSON objects + /// public abstract class JsonToSimplChildObjectBase : IKeyed { - /// - /// Notifies of bool change - /// + /// + /// Notifies of bool change + /// public event EventHandler BoolChange; - /// - /// Notifies of ushort change - /// + /// + /// Notifies of ushort change + /// public event EventHandler UShortChange; - /// - /// Notifies of string change - /// + /// + /// Notifies of string change + /// public event EventHandler StringChange; - /// - /// Delegate to get all values - /// + /// + /// Delegate to get all values + /// public SPlusValuesDelegate GetAllValuesDelegate { get; set; } /// @@ -36,9 +33,9 @@ namespace PepperDash.Core.JsonToSimpl; /// public SPlusValuesDelegate SetAllPathsDelegate { get; set; } - /// - /// Unique identifier for instance - /// + /// + /// Gets or sets the Key + /// public string Key { get; protected set; } /// @@ -52,33 +49,33 @@ namespace PepperDash.Core.JsonToSimpl; /// public string PathSuffix { get; protected set; } - /// - /// Indicates if the instance is linked to an object - /// + /// + /// Gets or sets the LinkedToObject + /// public bool LinkedToObject { get; protected set; } - /// - /// Reference to Master instance - /// + /// + /// Reference to Master instance + /// protected JsonToSimplMaster Master; - /// - /// Paths to boolean values in JSON structure - /// - protected Dictionary BoolPaths = new Dictionary(); - /// - /// Paths to numeric values in JSON structure - /// + /// + /// Paths to boolean values in JSON structure + /// + protected Dictionary BoolPaths = new Dictionary(); + /// + /// Paths to numeric values in JSON structure + /// protected Dictionary UshortPaths = new Dictionary(); - /// - /// Paths to string values in JSON structure - /// + /// + /// Paths to string values in JSON structure + /// protected Dictionary StringPaths = new Dictionary(); /// /// Call this before doing anything else /// - /// + /// /// /// /// @@ -92,13 +89,16 @@ namespace PepperDash.Core.JsonToSimpl; if (Master != null) Master.AddChild(this); else - this.LogWarning("JSON Child [{0}] cannot link to master {1}", key, masterUniqueId); + Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId); } - /// - /// Sets the path prefix for the object - /// - /// + /// + /// Sets the path prefix for the object + /// + /// + /// + /// SetPathPrefix method + /// public void SetPathPrefix(string pathPrefix) { PathPrefix = pathPrefix; @@ -108,7 +108,7 @@ namespace PepperDash.Core.JsonToSimpl; /// public void SetBoolPath(ushort index, string path) { - this.LogDebug("JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path); + Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path); if (path == null || path.Trim() == string.Empty) return; BoolPaths[index] = path; } @@ -118,7 +118,7 @@ namespace PepperDash.Core.JsonToSimpl; /// public void SetUshortPath(ushort index, string path) { - this.LogDebug("JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path); + Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path); if (path == null || path.Trim() == string.Empty) return; UshortPaths[index] = path; } @@ -128,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl; /// public void SetStringPath(ushort index, string path) { - this.LogDebug("JSON Child[{0}] SetStringPath {1}={2}", Key, index, path); + Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path); if (path == null || path.Trim() == string.Empty) return; StringPaths[index] = path; } @@ -141,13 +141,13 @@ namespace PepperDash.Core.JsonToSimpl; { if (!LinkedToObject) { - this.LogDebug("Not linked to object in file. Skipping"); + Debug.Console(1, this, "Not linked to object in file. Skipping"); return; } if (SetAllPathsDelegate == null) { - this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll"); + Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll"); return; } SetAllPathsDelegate(); @@ -173,18 +173,18 @@ namespace PepperDash.Core.JsonToSimpl; } // Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event - void ProcessUshortPath(ushort index) { - string response; - if (Process(UshortPaths[index], out response)) { - ushort val; - try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); } - catch { val = 0; } + void ProcessUshortPath(ushort index) { + string response; + if (Process(UshortPaths[index], out response)) { + ushort val; + try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); } + catch { val = 0; } - OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange); + OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange); + } + else { } + // OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange); } - else { } - // OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange); - } // Processes the path to a string property and fires of a StringChange event. void ProcessStringPath(ushort index) @@ -207,11 +207,11 @@ namespace PepperDash.Core.JsonToSimpl; bool Process(string path, out string response) { path = GetFullPath(path); - this.LogDebug("JSON Child[{0}] Processing {1}", Key, path); + Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path); response = ""; if (Master == null) { - this.LogWarning("JSONChild[{0}] cannot process without Master attached", Key); + Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key); return false; } @@ -233,8 +233,8 @@ namespace PepperDash.Core.JsonToSimpl; if (isCount) response = (t.HasValues ? t.Children().Count() : 0).ToString(); else - response = (string)t; - this.LogDebug(" ='{0}'", response); + response = t.Value(); + Debug.Console(1, " ='{0}'", response); return true; } } @@ -260,13 +260,13 @@ namespace PepperDash.Core.JsonToSimpl; { if (!LinkedToObject) { - this.LogDebug("Not linked to object in file. Skipping"); + Debug.Console(1, this, "Not linked to object in file. Skipping"); return; } if (SetAllPathsDelegate == null) { - this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster"); + Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster"); return; } SetAllPathsDelegate(); @@ -275,60 +275,75 @@ namespace PepperDash.Core.JsonToSimpl; GetAllValuesDelegate(); } - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// USetBoolValue method + /// public void USetBoolValue(ushort key, ushort theValue) { SetBoolValue(key, theValue == 1); } - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// SetBoolValue method + /// public void SetBoolValue(ushort key, bool theValue) { if (BoolPaths.ContainsKey(key)) SetValueOnMaster(BoolPaths[key], new JValue(theValue)); } - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// SetUShortValue method + /// public void SetUShortValue(ushort key, ushort theValue) { if (UshortPaths.ContainsKey(key)) SetValueOnMaster(UshortPaths[key], new JValue(theValue)); } - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// SetStringValue method + /// public void SetStringValue(ushort key, string theValue) { if (StringPaths.ContainsKey(key)) SetValueOnMaster(StringPaths[key], new JValue(theValue)); } - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// SetValueOnMaster method + /// public void SetValueOnMaster(string keyPath, JValue valueToSave) { var path = GetFullPath(keyPath); try { - this.LogDebug("JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave); + Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave); //var token = Master.JsonObject.SelectToken(path); //if (token != null) // The path exists in the file @@ -336,7 +351,7 @@ namespace PepperDash.Core.JsonToSimpl; } catch (Exception e) { - this.LogDebug("JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e); + Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e); } } @@ -352,12 +367,12 @@ namespace PepperDash.Core.JsonToSimpl; // Helpers for events //****************************************************************************************** - /// - /// Event helper - /// - /// - /// - /// + /// + /// Event helper + /// + /// + /// + /// protected void OnBoolChange(bool state, ushort index, ushort type) { var handler = BoolChange; @@ -370,12 +385,12 @@ namespace PepperDash.Core.JsonToSimpl; } //****************************************************************************************** - /// - /// Event helper - /// - /// - /// - /// + /// + /// Event helper + /// + /// + /// + /// protected void OnUShortChange(ushort state, ushort index, ushort type) { var handler = UShortChange; @@ -387,12 +402,12 @@ namespace PepperDash.Core.JsonToSimpl; } } - /// - /// Event helper - /// - /// - /// - /// + /// + /// Event helper + /// + /// + /// + /// protected void OnStringChange(string value, ushort index, ushort type) { var handler = StringChange; @@ -403,4 +418,5 @@ namespace PepperDash.Core.JsonToSimpl; StringChange(this, args); } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplFileMaster.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplFileMaster.cs index 1cf9efcf..13354fdd 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplFileMaster.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplFileMaster.cs @@ -1,263 +1,292 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using PepperDash.Core.Logging; -using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; +using Newtonsoft.Json.Linq; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Represents a JSON file that can be read and written to -/// -public class JsonToSimplFileMaster : JsonToSimplMaster +namespace PepperDash.Core.JsonToSimpl { /// - /// Sets the filepath as well as registers this with the Global.Masters list + /// Represents a JSON file that can be read and written to /// - public string Filepath { get; private set; } - - /// - /// Filepath to the actual file that will be read (Portal or local) - /// - public string ActualFilePath { get; private set; } - - /// - /// - /// - public string Filename { get; private set; } - /// - /// - /// - public string FilePathName { get; private set; } - - /*****************************************************************************************/ - /** Privates **/ - - - // The JSON file in JObject form - // For gathering the incoming data - object StringBuilderLock = new object(); - // To prevent multiple same-file access - static object FileLock = new object(); - - /*****************************************************************************************/ - - /// - /// SIMPL+ default constructor. - /// - public JsonToSimplFileMaster() + public class JsonToSimplFileMaster : JsonToSimplMaster { - } + /// + /// Sets the filepath as well as registers this with the Global.Masters list + /// + public string Filepath { get; private set; } - /// - /// Read, evaluate and udpate status - /// - public void EvaluateFile(string filepath) - { - try + /// + /// Gets or sets the ActualFilePath + /// + public string ActualFilePath { get; private set; } + + /// + /// Gets or sets the Filename + /// + public string Filename { get; private set; } + /// + /// + /// + public string FilePathName { get; private set; } + + /*****************************************************************************************/ + /** Privates **/ + + + // The JSON file in JObject form + // For gathering the incoming data + object StringBuilderLock = new object(); + // To prevent multiple same-file access + static object FileLock = new object(); + + /*****************************************************************************************/ + + /// + /// SIMPL+ default constructor. + /// + public JsonToSimplFileMaster() { - OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); + } - var dirSeparator = Path.DirectorySeparatorChar; - var dirSeparatorAlt = Path.AltDirectorySeparatorChar; - - var series = CrestronEnvironment.ProgramCompatibility; - - var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3)); - OnBoolChange(is3Series, 0, - JsonToSimplConstants.ProgramCompatibility3SeriesChange); - - var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4)); - OnBoolChange(is4Series, 0, - JsonToSimplConstants.ProgramCompatibility4SeriesChange); - - var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; - OnBoolChange(isServer, 0, - JsonToSimplConstants.DevicePlatformValueChange); - - // get the roomID - var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId; - if (!string.IsNullOrEmpty(roomId)) + /// + /// Read, evaluate and udpate status + /// + public void EvaluateFile(string filepath) + { + try { - OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange); - } + OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); - // get the roomName - var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName; - if (!string.IsNullOrEmpty(roomName)) - { - OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange); - } + var dirSeparator = Path.DirectorySeparatorChar; + var dirSeparatorAlt = Path.AltDirectorySeparatorChar; - var rootDirectory = Directory.GetApplicationRootDirectory(); - OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange); - - var splusPath = string.Empty; - if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase)) - { - if (is4Series) - splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase); - else if (isServer) - splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase); + var series = CrestronEnvironment.ProgramCompatibility; + + var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3)); + OnBoolChange(is3Series, 0, + JsonToSimplConstants.ProgramCompatibility3SeriesChange); + + var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4)); + OnBoolChange(is4Series, 0, + JsonToSimplConstants.ProgramCompatibility4SeriesChange); + + var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; + OnBoolChange(isServer, 0, + JsonToSimplConstants.DevicePlatformValueChange); + + // get the roomID + var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId; + if (!string.IsNullOrEmpty(roomId)) + { + OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange); + } + + // get the roomName + var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName; + if (!string.IsNullOrEmpty(roomName)) + { + OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange); + } + + var rootDirectory = Directory.GetApplicationRootDirectory(); + OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange); + + var splusPath = string.Empty; + if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase)) + { + if (is4Series) + splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase); + else if (isServer) + splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase); + else + splusPath = filepath; + } + + filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator); + + Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory, + filepath.TrimStart(dirSeparator, dirSeparatorAlt)); + + OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange); + + if (string.IsNullOrEmpty(Filepath)) + { + OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); + return; + } + + // get file directory and name to search + var fileDirectory = Path.GetDirectoryName(Filepath); + var fileName = Path.GetFileName(Filepath); + + OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName); + + if (Directory.Exists(fileDirectory)) + { + // get the directory info + var directoryInfo = new DirectoryInfo(fileDirectory); + + // get the file to be read + var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault(); + if (actualFile == null) + { + var msg = string.Format("JSON file not found: {0}", Filepath); + OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + return; + } + + // \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json + // \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json + ActualFilePath = actualFile.FullName; + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "Actual JSON file is {0}", ActualFilePath); + + Filename = actualFile.Name; + OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange); + OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "JSON Filename is {0}", Filename); + + + FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator); + OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange); + OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "JSON File Path is {0}", FilePathName); + + var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); + + JsonObject = JObject.Parse(json); + foreach (var child in Children) + child.ProcessAll(); + + OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); + } else - splusPath = filepath; + { + OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "'{0}' not found", fileDirectory); + } + } + catch (Exception e) + { + var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message); + OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + + var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace); + OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(stackTrace); + ErrorLog.Error(stackTrace); + } + } + + + /// + /// Sets the debug level + /// + /// + /// + /// setDebugLevel method + /// + public void setDebugLevel(uint level) + { + Debug.SetDebugLevel(level); + } + + /// + /// Saves the values to the file + /// + public override void Save() + { + // this code is duplicated in the other masters!!!!!!!!!!!!! + UnsavedValues = new Dictionary(); + // Make each child update their values into master object + foreach (var child in Children) + { + Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); + child.UpdateInputsForMaster(); } - filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator); - - Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory, - filepath.TrimStart(dirSeparator, dirSeparatorAlt)); - - OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange); - - if (string.IsNullOrEmpty(Filepath)) + if (UnsavedValues == null || UnsavedValues.Count == 0) { - OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); + Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); return; } - - // get file directory and name to search - var fileDirectory = Path.GetDirectoryName(Filepath); - var fileName = Path.GetFileName(Filepath); - - OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange); - this.LogInformation("Checking '{0}' for '{1}'", fileDirectory, fileName); - - if (Directory.Exists(fileDirectory)) + lock (FileLock) { - // get the directory info - var directoryInfo = new DirectoryInfo(fileDirectory); - - // get the file to be read - var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault(); - if (actualFile == null) + Debug.Console(1, "Saving"); + foreach (var path in UnsavedValues.Keys) { - var msg = string.Format("JSON file not found: {0}", Filepath); - OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(msg); - this.LogError(msg); - return; + var tokenToReplace = JsonObject.SelectToken(path); + if (tokenToReplace != null) + {// It's found + tokenToReplace.Replace(UnsavedValues[path]); + Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); + } + else // No token. Let's make one + { + //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net + Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); + + // JContainer jpart = JsonObject; + // // walk down the path and find where it goes + //#warning Does not handle arrays. + // foreach (var part in path.Split('.')) + // { + + // var openPos = part.IndexOf('['); + // if (openPos > -1) + // { + // openPos++; // move to number + // var closePos = part.IndexOf(']'); + // var arrayName = part.Substring(0, openPos - 1); // get the name + // var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos)); + + // // Check if the array itself exists and add the item if so + // if (jpart[arrayName] != null) + // { + // var arrayObj = jpart[arrayName] as JArray; + // var item = arrayObj[index]; + // if (item == null) + // arrayObj.Add(new JObject()); + // } + + // Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW"); + // continue; + // } + // // Build the + // if (jpart[part] == null) + // jpart.Add(new JProperty(part, new JObject())); + // jpart = jpart[part] as JContainer; + // } + // jpart.Replace(UnsavedValues[path]); + } } - - // \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json - // \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json - ActualFilePath = actualFile.FullName; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); - OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange); - this.LogInformation("Actual JSON file is {0}", ActualFilePath); - - Filename = actualFile.Name; - OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange); - OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange); - this.LogInformation("JSON Filename is {0}", Filename); - - - FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator); - OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange); - OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange); - this.LogInformation("JSON File Path is {0}", FilePathName); - - var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); - - JsonObject = JObject.Parse(json); - foreach (var child in Children) - child.ProcessAll(); - - OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); - } - else - { - OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange); - this.LogError("'{0}' not found", fileDirectory); - } - } - catch (Exception e) - { - var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message); - OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(msg); - this.LogException(e, "EvaluateFile Exception: {0}", e.Message); - - var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace); - OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(stackTrace); - this.LogVerbose("EvaluateFile: Stack Trace\r{0}", e.StackTrace); - } - } - - - /// - /// Sets the debug level - /// - /// - public void setDebugLevel(uint level) - { - Debug.SetDebugLevel(level); - } - - /// - /// Saves the values to the file - /// - public override void Save() - { - // this code is duplicated in the other masters!!!!!!!!!!!!! - UnsavedValues = new Dictionary(); - // Make each child update their values into master object - foreach (var child in Children) - { - this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); - child.UpdateInputsForMaster(); - } - - if (UnsavedValues == null || UnsavedValues.Count == 0) - { - this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID); - return; - } - lock (FileLock) - { - this.LogInformation("Saving"); - foreach (var path in UnsavedValues.Keys) - { - var tokenToReplace = JsonObject.SelectToken(path); - if (tokenToReplace != null) - {// It's found - tokenToReplace.Replace(UnsavedValues[path]); - this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path); - } - else // No token. Let's make one + using (StreamWriter sw = new StreamWriter(ActualFilePath)) { - //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net - this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); - - } - } - using (StreamWriter sw = new StreamWriter(ActualFilePath)) - { - try - { - sw.Write(JsonObject.ToString()); - sw.Flush(); - } - catch (Exception e) - { - string err = string.Format("Error writing JSON file:\r{0}", e); - this.LogException(e, "Error writing JSON file: {0}", e.Message); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); - return; + try + { + sw.Write(JsonObject.ToString()); + sw.Flush(); + } + catch (Exception e) + { + string err = string.Format("Error writing JSON file:\r{0}", e); + Debug.Console(0, err); + ErrorLog.Warn(err); + return; + } } } } } } - - diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplFixedPathObject.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplFixedPathObject.cs index 6338e309..9fa8ba01 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplFixedPathObject.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplFixedPathObject.cs @@ -1,17 +1,18 @@  -namespace PepperDash.Core.JsonToSimpl; - -/// -/// -/// +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Represents a JsonToSimplFixedPathObject + /// public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase { - /// - /// Constructor - /// + /// + /// Constructor + /// public JsonToSimplFixedPathObject() { this.LinkedToObject = true; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplGenericMaster.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplGenericMaster.cs index 423c435a..14f7eaa8 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplGenericMaster.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplGenericMaster.cs @@ -1,42 +1,37 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; using Crestron.SimplSharp; -using PepperDash.Core.Logging; -using Renci.SshNet.Messages; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; +using Newtonsoft.Json.Linq; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Generic Master -/// - public class JsonToSimplGenericMaster : JsonToSimplMaster +namespace PepperDash.Core.JsonToSimpl { + /// + /// Represents a JsonToSimplGenericMaster + /// + public class JsonToSimplGenericMaster : JsonToSimplMaster + { /*****************************************************************************************/ /** Privates **/ - + // The JSON file in JObject form // For gathering the incoming data object StringBuilderLock = new object(); // To prevent multiple same-file access static object WriteLock = new object(); - /// - /// Callback action for saving - /// + /// + /// Gets or sets the SaveCallback + /// public Action SaveCallback { get; set; } /*****************************************************************************************/ /// - /// SIMPL+ default constructor. - /// + /// SIMPL+ default constructor. + /// public JsonToSimplGenericMaster() - { + { } /// @@ -76,8 +71,7 @@ namespace PepperDash.Core.JsonToSimpl; } catch (Exception e) { - this.LogException(e, "JSON parsing failed:\r{0}", e.Message); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); + Debug.Console(0, this, "JSON parsing failed:\r{0}", e); } } @@ -88,40 +82,41 @@ namespace PepperDash.Core.JsonToSimpl; public override void Save() { // this code is duplicated in the other masters!!!!!!!!!!!!! - UnsavedValues = new Dictionary(); + UnsavedValues = new Dictionary(); // Make each child update their values into master object foreach (var child in Children) { - this.LogDebug("Master. checking child [{0}] for updates to save", child.Key); + Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key); child.UpdateInputsForMaster(); } if (UnsavedValues == null || UnsavedValues.Count == 0) { - this.LogDebug("Master. No updated values to save. Skipping"); + Debug.Console(1, this, "Master. No updated values to save. Skipping"); return; } lock (WriteLock) { - this.LogDebug("Saving"); + Debug.Console(1, this, "Saving"); foreach (var path in UnsavedValues.Keys) { var tokenToReplace = JsonObject.SelectToken(path); if (tokenToReplace != null) {// It's found tokenToReplace.Replace(UnsavedValues[path]); - this.LogDebug("Master Updating '{0}'", path); + Debug.Console(1, this, "Master Updating '{0}'", path); } else // No token. Let's make one { - this.LogDebug("Master Cannot write value onto missing property: '{0}'", path); + Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path); } } } if (SaveCallback != null) SaveCallback(JsonObject.ToString()); else - this.LogDebug("WARNING: No save callback defined."); + Debug.Console(0, this, "WARNING: No save callback defined."); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplMaster.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplMaster.cs index 2b2a504f..b3fa92a3 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplMaster.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplMaster.cs @@ -1,40 +1,34 @@ -extern alias NewtonsoftJson; - using System; using System.Collections.Generic; using System.IO; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; -using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException; -using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader; -using PepperDash.Core.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Abstract base class for JsonToSimpl interactions -/// +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Abstract base class for JsonToSimpl interactions + /// public abstract class JsonToSimplMaster : IKeyed { - /// - /// Notifies of bool change - /// + /// + /// Notifies of bool change + /// public event EventHandler BoolChange; - /// - /// Notifies of ushort change - /// + /// + /// Notifies of ushort change + /// public event EventHandler UshrtChange; - /// - /// Notifies of string change - /// + /// + /// Notifies of string change + /// public event EventHandler StringChange; - /// - /// A collection of associated child modules - /// + /// + /// A collection of associated child modules + /// protected List Children = new List(); /*****************************************************************************************/ @@ -44,9 +38,9 @@ namespace PepperDash.Core.JsonToSimpl; /// public string Key { get { return UniqueID; } } - /// - /// A unique ID - /// + /// + /// Gets or sets the UniqueID + /// public string UniqueID { get; protected set; } /// @@ -88,9 +82,9 @@ namespace PepperDash.Core.JsonToSimpl; } } - /// - /// - /// + /// + /// Gets or sets the JsonObject + /// public JObject JsonObject { get; protected set; } /*****************************************************************************************/ @@ -143,15 +137,16 @@ namespace PepperDash.Core.JsonToSimpl; { if (UnsavedValues.ContainsKey(path)) { - this.LogWarning("Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path); + Debug.Console(0, "Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path); } else UnsavedValues.Add(path, value); + //Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count); } - /// - /// Saves the file - /// + /// + /// Saves the file + /// public abstract void Save(); @@ -160,14 +155,18 @@ namespace PepperDash.Core.JsonToSimpl; /// public static class JsonFixes { - /// - /// Deserializes a string into a JObject - /// - /// - /// + /// + /// Deserializes a string into a JObject + /// + /// + /// public static JObject ParseObject(string json) { - using (var reader = new JsonTextReader(new System.IO.StringReader(json))) + #if NET6_0 + using (var reader = new JsonTextReader(new System.IO.StringReader(json))) +#else + using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json))) +#endif { var startDepth = reader.Depth; var obj = JObject.Load(reader); @@ -177,15 +176,21 @@ namespace PepperDash.Core.JsonToSimpl; } } - /// - /// Deserializes a string into a JArray - /// - /// - /// + /// + /// Deserializes a string into a JArray + /// + /// + /// + /// + /// ParseArray method + /// public static JArray ParseArray(string json) { - - using (var reader = new JsonTextReader(new System.IO.StringReader(json))) + #if NET6_0 + using (var reader = new JsonTextReader(new System.IO.StringReader(json))) +#else + using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json))) +#endif { var startDepth = reader.Depth; var obj = JArray.Load(reader); @@ -228,12 +233,12 @@ namespace PepperDash.Core.JsonToSimpl; } } - /// - /// Helper event - /// - /// - /// - /// + /// + /// Helper event + /// + /// + /// + /// protected void OnStringChange(string value, ushort index, ushort type) { if (StringChange != null) @@ -244,3 +249,4 @@ namespace PepperDash.Core.JsonToSimpl; } } } +} diff --git a/src/PepperDash.Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs b/src/PepperDash.Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs index bbd8df2c..e142d1bc 100644 --- a/src/PepperDash.Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs +++ b/src/PepperDash.Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs @@ -1,31 +1,27 @@ -extern alias NewtonsoftJson; - -using System; +using System; using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; +using Newtonsoft.Json.Linq; using PepperDash.Core.Config; -using PepperDash.Core.Logging; -namespace PepperDash.Core.JsonToSimpl; - -/// -/// Portal File Master -/// -public class JsonToSimplPortalFileMaster : JsonToSimplMaster +namespace PepperDash.Core.JsonToSimpl { + /// + /// Portal File Master + /// + public class JsonToSimplPortalFileMaster : JsonToSimplMaster + { /// /// Sets the filepath as well as registers this with the Global.Masters list /// public string PortalFilepath { get; private set; } - /// - /// File path of the actual file being read (Portal or local) - /// - public string ActualFilePath { get; private set; } + /// + /// Gets or sets the ActualFilePath + /// + public string ActualFilePath { get; private set; } /*****************************************************************************************/ /** Privates **/ @@ -37,10 +33,10 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster /*****************************************************************************************/ /// - /// SIMPL+ default constructor. - /// + /// SIMPL+ default constructor. + /// public JsonToSimplPortalFileMaster() - { + { } /// @@ -62,19 +58,19 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster // If the portal file is xyz.json, then // the file we want to check for first will be called xyz.local.json var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json"); - this.LogInformation("Checking for local file {0}", localFilepath); + Debug.Console(0, this, "Checking for local file {0}", localFilepath); var actualLocalFile = GetActualFileInfoFromPath(localFilepath); if (actualLocalFile != null) { ActualFilePath = actualLocalFile.FullName; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); } // If the local file does not exist, then read the portal file xyz.json // and create the local. else { - this.LogInformation("Local JSON file not found {0}\rLoading portal JSON file", localFilepath); + Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath); var actualPortalFile = GetActualFileInfoFromPath(portalFilepath); if (actualPortalFile != null) { @@ -82,18 +78,19 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster // got the portal file, hand off to the merge / save method PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath); ActualFilePath = newLocalPath; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); } else { var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath); - this.LogError(msg); + Debug.Console(1, this, msg); + ErrorLog.Error(msg); return; } } // At this point we should have a local file. Do it. - this.LogInformation("Reading local JSON file {0}", ActualFilePath); + Debug.Console(1, "Reading local JSON file {0}", ActualFilePath); string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); @@ -107,7 +104,8 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster catch (Exception e) { var msg = string.Format("JSON parsing failed:\r{0}", e); - this.LogError(msg); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); return; } } @@ -148,30 +146,30 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster // Make each child update their values into master object foreach (var child in Children) { - this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); + Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); child.UpdateInputsForMaster(); } if (UnsavedValues == null || UnsavedValues.Count == 0) { - this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID); + Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); return; } lock (FileLock) { - this.LogInformation("Saving"); + Debug.Console(1, "Saving"); foreach (var path in UnsavedValues.Keys) { var tokenToReplace = JsonObject.SelectToken(path); if (tokenToReplace != null) {// It's found tokenToReplace.Replace(UnsavedValues[path]); - this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path); + Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); } else // No token. Let's make one { //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net - this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); + Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); } } @@ -185,11 +183,12 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster catch (Exception e) { string err = string.Format("Error writing JSON file:\r{0}", e); - this.LogException(e, "Error writing JSON file: {0}", e.Message); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); + Debug.Console(0, err); + ErrorLog.Warn(err); return; } } } } } +} diff --git a/src/PepperDash.Core/Logging/CrestronEnricher.cs b/src/PepperDash.Core/Logging/CrestronEnricher.cs index 65369957..c25306ca 100644 --- a/src/PepperDash.Core/Logging/CrestronEnricher.cs +++ b/src/PepperDash.Core/Logging/CrestronEnricher.cs @@ -7,38 +7,37 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Core.Logging; - -/// -/// Enriches log events with Crestron-specific context properties, such as the application name based on the device platform. -/// -public class CrestronEnricher : ILogEventEnricher +namespace PepperDash.Core.Logging { - static readonly string _appName; - - static CrestronEnricher() + /// + /// Represents a CrestronEnricher + /// + public class CrestronEnricher : ILogEventEnricher { - switch (CrestronEnvironment.DevicePlatform) + static readonly string _appName; + + static CrestronEnricher() { - case eDevicePlatform.Appliance: - _appName = $"App {InitialParametersClass.ApplicationNumber}"; - break; - case eDevicePlatform.Server: - _appName = $"{InitialParametersClass.RoomId}"; - break; + switch (CrestronEnvironment.DevicePlatform) + { + case eDevicePlatform.Appliance: + _appName = $"App {InitialParametersClass.ApplicationNumber}"; + break; + case eDevicePlatform.Server: + _appName = $"{InitialParametersClass.RoomId}"; + break; + } + } + + + /// + /// Enrich method + /// + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + var property = propertyFactory.CreateProperty("App", _appName); + + logEvent.AddOrUpdateProperty(property); } } - - - /// - /// Enriches the log event with Crestron-specific properties. - /// - /// - /// - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) - { - var property = propertyFactory.CreateProperty("App", _appName); - - logEvent.AddOrUpdateProperty(property); - } } diff --git a/src/PepperDash.Core/Logging/Debug.cs b/src/PepperDash.Core/Logging/Debug.cs index fcb262b5..97ac5890 100644 --- a/src/PepperDash.Core/Logging/Debug.cs +++ b/src/PepperDash.Core/Logging/Debug.cs @@ -1,14 +1,12 @@ -extern alias NewtonsoftJson; - using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; -using System.Timers; using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronLogger; -using PdCore = PepperDash.Core.Abstractions; +using Newtonsoft.Json; using PepperDash.Core.Logging; using Serilog; using Serilog.Context; @@ -18,1156 +16,1224 @@ using Serilog.Formatting.Compact; using Serilog.Formatting.Json; using Serilog.Templates; -namespace PepperDash.Core; - -/// -/// Contains debug commands for use in various situations -/// -public static class Debug +namespace PepperDash.Core { - private static readonly string LevelStoreKey = "ConsoleDebugLevel"; - private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel"; - private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel"; - private static readonly string FileLevelStoreKey = "FileDebugLevel"; - private static readonly string DoNotLoadOnNextBootKey = "DoNotLoadOnNextBoot"; - - private static readonly Dictionary _logLevels = new Dictionary() + /// + /// + public static class Debug { - {0, LogEventLevel.Information }, - {3, LogEventLevel.Warning }, - {4, LogEventLevel.Error }, - {5, LogEventLevel.Fatal }, - {1, LogEventLevel.Debug }, - {2, LogEventLevel.Verbose }, - }; + private static readonly string LevelStoreKey = "ConsoleDebugLevel"; + private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel"; + private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel"; + private static readonly string FileLevelStoreKey = "FileDebugLevel"; - private static ILogger _logger; - - // Injected service abstractions. Populated by DebugServiceRegistration.Register() - // before the Debug static constructor runs. Null when running on hardware without - // pre-registration (the static ctor falls back to the Crestron SDK directly). - private static PdCore.ICrestronEnvironment _environment; - private static PdCore.ICrestronConsole _console; - private static PdCore.ICrestronDataStore _dataStore; - - private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch; - - private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch; - - private static readonly LoggingLevelSwitch errorLogLevelSwitch; - - private static readonly LoggingLevelSwitch fileLoggingLevelSwitch; - - /// - /// The minimum log level for messages to be sent to the console sink - /// - public static LogEventLevel WebsocketMinimumLogLevel - { - get { return websocketLoggingLevelSwitch.MinimumLevel; } - } - - private static readonly DebugWebsocketSink websocketSink; - - /// - /// The DebugWebsocketSink instance used for sending log messages to connected websocket clients. - /// This is exposed publicly in case there is a need to call methods on the sink directly, such as SendMessageToClients. - /// For general logging purposes, use the LogMessage and LogError methods in this class which will send messages to all configured sinks including the websocket sink. - /// - public static DebugWebsocketSink WebsocketSink - { - get { return websocketSink; } - } - - /// - /// Describes the folder location where a given program stores it's debug level memory. By default, the - /// file written will be named appNdebug where N is 1-10. - /// - public static string OldFilePathPrefix = @"\nvram\debug\"; - - /// - /// Describes the new folder location where a given program stores it's debug level memory. By default, the - /// file written will be named appNdebug where N is 1-10. - /// - public static string NewFilePathPrefix = @"\user\debug\"; - - /// - /// The name of the file containing the current debug settings. - /// - public static string FileName = "app0Debug.json"; // default; updated in static ctor using _environment.ApplicationNumber - - /// - /// Debug level to set for a given program. - /// - public static int Level { get; private set; } - - /// - /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal - /// - public static bool DoNotLoadConfigOnNextBoot { get; private set; } - - private static DebugContextCollection _contexts; - - private const int SaveTimeoutMs = 30000; - - /// - /// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences - /// - public static bool IsRunningOnAppliance; // set in static constructor - - /// - /// Version for the currently loaded PepperDashCore dll - /// - public static string PepperDashCoreVersion { get; private set; } - - // private static Timer _saveTimer; - - - private const int defaultConsoleDebugTimeoutMin = 120; - - private static Timer consoleDebugTimer; - - /// - /// When true, the IncludedExcludedKeys dict will contain keys to include. - /// When false (default), IncludedExcludedKeys will contain keys to exclude. - /// - private static bool _excludeAllMode; - - //static bool ExcludeNoKeyMessages; - - private static readonly Dictionary IncludedExcludedKeys; - - private static readonly LoggerConfiguration _defaultLoggerConfiguration; - - private static LoggerConfiguration _loggerConfiguration; - - /// - /// The default logger configuration used by the Debug class. Can be used as a base for creating custom logger configurations. - /// If changes are made to this configuration after initialization, call ResetLoggerConfiguration to have those changes reflected in the logger. - /// - public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; - - static Debug() - { - try + private static readonly Dictionary _logLevels = new Dictionary() { - // Pick up services pre-registered by the composition root (or test setup). - // If null, fall back to direct Crestron SDK calls for production hardware. - _environment = PdCore.DebugServiceRegistration.Environment; - _console = PdCore.DebugServiceRegistration.Console; - _dataStore = PdCore.DebugServiceRegistration.DataStore; + {0, LogEventLevel.Information }, + {3, LogEventLevel.Warning }, + {4, LogEventLevel.Error }, + {5, LogEventLevel.Fatal }, + {1, LogEventLevel.Debug }, + {2, LogEventLevel.Verbose }, + }; - IsRunningOnAppliance = _environment?.DevicePlatform == PdCore.DevicePlatform.Appliance; + private static ILogger _logger; - // Update FileName now that _environment is available (avoids Crestron SDK ref in field initializer). - FileName = $"app{_environment?.ApplicationNumber ?? 0}Debug.json"; + private static readonly LoggingLevelSwitch _consoleLogLevelSwitch; - _dataStore?.InitStore(); + private static readonly LoggingLevelSwitch _websocketLogLevelSwitch; - consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false }; - consoleDebugTimer.Elapsed += (s, e) => - { - SetDebugLevel(LogEventLevel.Information); - _console?.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes"); - }; + private static readonly LoggingLevelSwitch _errorLogLevelSwitch; + + private static readonly LoggingLevelSwitch _fileLogLevelSwitch; + + /// + /// Gets the minimum log level for the websocket sink. + /// + public static LogEventLevel WebsocketMinimumLogLevel + { + get { return _websocketLogLevelSwitch.MinimumLevel; } + } + + private static readonly DebugWebsocketSink _websocketSink; + + /// + /// Gets the websocket sink for debug logging. + /// + public static DebugWebsocketSink WebsocketSink + { + get { return _websocketSink; } + } + + /// + /// Describes the folder location where a given program stores it's debug level memory. By default, the + /// file written will be named appNdebug where N is 1-10. + /// + public static string OldFilePathPrefix = @"\nvram\debug\"; + + /// + /// Describes the new folder location where a given program stores it's debug level memory. By default, the + /// file written will be named appNdebug where N is 1-10. + /// + public static string NewFilePathPrefix = @"\user\debug\"; + + /// + /// The name of the file containing the current debug settings. + /// + public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); + + /// + /// Gets or sets the Level + /// + public static int Level { get; private set; } + + /// + /// Gets or sets the DoNotLoadConfigOnNextBoot + /// + public static bool DoNotLoadConfigOnNextBoot { get; private set; } + + private static DebugContextCollection _contexts; + + private const int SaveTimeoutMs = 30000; + + /// + /// Indicates whether the system is running on an appliance. + /// + public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; + + /// + /// Gets or sets the PepperDashCoreVersion + /// + public static string PepperDashCoreVersion { get; private set; } + + private static CTimer _saveTimer; + + /// + /// When true, the IncludedExcludedKeys dict will contain keys to include. + /// When false (default), IncludedExcludedKeys will contain keys to exclude. + /// + private static bool _excludeAllMode; + + private static readonly Dictionary IncludedExcludedKeys; + + private static readonly LoggerConfiguration _defaultLoggerConfiguration; + + private static LoggerConfiguration _loggerConfiguration; + + /// + /// Gets the logger configuration for the debug logging. + /// + public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; + + static Debug() + { + CrestronDataStoreStatic.InitCrestronDataStore(); var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey); + var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey); + var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey); + var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); - consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); - websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); - errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); - fileLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); + _consoleLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); - websocketSink = TryCreateWebsocketSink(); + _websocketLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); - var appRoot = _environment?.GetApplicationRootDirectory() - ?? System.IO.Path.GetTempPath(); - var sep = System.IO.Path.DirectorySeparatorChar; - var appNum = _environment?.ApplicationNumber ?? 0; - var roomId = _environment?.RoomId ?? 0; + _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); - var logFilePath = IsRunningOnAppliance - ? $"{appRoot}{sep}user{sep}debug{sep}app{appNum}{sep}global-log.log" - : $"{appRoot}{sep}user{sep}debug{sep}room{roomId}{sep}global-log.log"; + _fileLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); - _console?.PrintLine($"Saving log files to {logFilePath}"); + _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); + + var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? + $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" : + $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log"; + + CrestronConsole.PrintLine($"Saving log files to {logFilePath}"); + + var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance + ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}" + : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"; - // Build the base Serilog pipeline — sinks that require the Crestron SDK are - // added conditionally so the logger remains usable in test environments. _defaultLoggerConfiguration = new LoggerConfiguration() .MinimumLevel.Verbose() .Enrich.FromLogContext() - .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch) + .Enrich.With(new CrestronEnricher()) + .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLogLevelSwitch) + .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLogLevelSwitch) + .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Debug, - retainedFileCountLimit: IsRunningOnAppliance ? 30 : 60, - levelSwitch: fileLoggingLevelSwitch + retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14, + levelSwitch: _fileLogLevelSwitch ); - // Websocket sink is null when DebugWebsocketSink failed to construct (e.g. test env). - if (websocketSink != null) - _defaultLoggerConfiguration.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch); - - // Add Crestron-specific enricher and error-log sink only on real hardware. - if (_environment?.IsHardwareRuntime == true) + try { - var errorLogTemplate = IsRunningOnAppliance - ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}" - : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"; - - _defaultLoggerConfiguration - .Enrich.With(new CrestronEnricher()) - .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch); + if (InitialParametersClass.NumberOfRemovableDrives > 0) + { + CrestronConsole.PrintLine("{0} RM Drive(s) Present. Initializing CrestronLogger", InitialParametersClass.NumberOfRemovableDrives); + _defaultLoggerConfiguration.WriteTo.Sink(new DebugCrestronLoggerSink()); + } + else + CrestronConsole.PrintLine("No RM Drive(s) Present. Not using Crestron Logger"); + } + catch (Exception e) + { + CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e); } + // Instantiate the root logger _loggerConfiguration = _defaultLoggerConfiguration; - _logger = _loggerConfiguration.CreateLogger(); + _logger = _loggerConfiguration.CreateLogger(); + // Get the assembly version and print it to console and the log GetVersion(); - string msg = IsRunningOnAppliance - ? $"[App {appNum}] Using PepperDash_Core v{PepperDashCoreVersion}" - : $"[Room {roomId}] Using PepperDash_Core v{PepperDashCoreVersion}"; + string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}"; + + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) + { + msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}"; + } + + CrestronConsole.PrintLine(msg); - _console?.PrintLine(msg); LogMessage(LogEventLevel.Information, msg); IncludedExcludedKeys = new Dictionary(); - if (_environment?.RuntimeEnvironment == PdCore.RuntimeEnvironment.SimplSharpPro) + if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) { - _console?.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", - "donotloadonnextboot:P [true/false]: Should the application load on next boot", - PdCore.ConsoleAccessLevel.AccessOperator); + // Add command to console + CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", + "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); - _console?.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", + CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", "appdebug:P [0-5]: Sets the application's console debug message level", - PdCore.ConsoleAccessLevel.AccessOperator); - - _console?.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", "appdebuglog:P [all] Use \"all\" for full log.", - PdCore.ConsoleAccessLevel.AccessOperator); - - _console?.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", "appdebugclear:P Clears the current custom log", - PdCore.ConsoleAccessLevel.AccessOperator); - - _console?.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", - "appdebugfilter [params]", - PdCore.ConsoleAccessLevel.AccessOperator); + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", + "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); } - DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot(); + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + + LoadMemory(); + + var context = _contexts.GetOrCreateItem("DEFAULT"); + Level = context.Level; + DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot; if (DoNotLoadConfigOnNextBoot) - _console?.PrintLine($"Program {appNum} will not load config after next boot. Use console command go:{appNum} to load the config manually"); + CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); - consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) => + _errorLogLevelSwitch.MinimumLevelChanged += (sender, args) => { - LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", consoleLoggingLevelSwitch.MinimumLevel); + LogMessage(LogEventLevel.Information, "Error log debug level set to {minimumLevel}", _errorLogLevelSwitch.MinimumLevel); }; - errorLogLevelSwitch.MinimumLevelChanged += (sender, args) => - { - LogMessage(LogEventLevel.Information, "Error log debug level set to {minimumLevel}", errorLogLevelSwitch.MinimumLevel); - }; + // Set initial error log level based on platform && stored level. If appliance, use stored level, otherwise default to verbose + SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? _errorLogLevelSwitch.MinimumLevel : LogEventLevel.Verbose); } - catch (Exception ex) + + /// + /// UpdateLoggerConfiguration method + /// + public static void UpdateLoggerConfiguration(LoggerConfiguration config) { - // _logger may not have been initialized yet — do not call LogError here. - // _console may also be null; fall back to CrestronConsole as last resort. - // IMPORTANT: this catch block must not throw — any exception escaping a static - // constructor permanently faults the type, making the entire class unusable. - try { _console?.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); } + _loggerConfiguration = config; + + _logger = config.CreateLogger(); + } + + /// + /// ResetLoggerConfiguration method + /// + public static void ResetLoggerConfiguration() + { + _loggerConfiguration = _defaultLoggerConfiguration; + + _logger = _loggerConfiguration.CreateLogger(); + } + + private static LogEventLevel GetStoredLogEventLevel(string levelStoreKey) + { + try + { + var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel); + + if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n"); + + CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, levelStoreKey == ErrorLogLevelStoreKey ? (int)LogEventLevel.Warning : (int)LogEventLevel.Information); + + return levelStoreKey == ErrorLogLevelStoreKey ? LogEventLevel.Warning : LogEventLevel.Information; + } + + if (logLevel < 0 || logLevel > 5) + { + CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}"); + return LogEventLevel.Information; + } + + CrestronConsole.PrintLine($"Stored log level for {levelStoreKey} is {logLevel}"); + + return (LogEventLevel)logLevel; + } + catch (Exception ex) + { + CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); + return LogEventLevel.Information; + } + } + + private static void GetVersion() + { + var assembly = Assembly.GetExecutingAssembly(); + var ver = + assembly + .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); + + if (ver != null && ver.Length > 0) + { + if (ver[0] is AssemblyInformationalVersionAttribute verAttribute) + { + PepperDashCoreVersion = verAttribute.InformationalVersion; + } + } + else + { + var version = assembly.GetName().Version; + PepperDashCoreVersion = version.ToString(); + } + } + + /// + /// Used to save memory when shutting down + /// + /// + static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + + if (programEventType == eProgramStatusEventType.Stopping) + { + Log.CloseAndFlush(); + + if (_saveTimer != null) + { + _saveTimer.Stop(); + _saveTimer = null; + } + LogMessage(LogEventLevel.Information, "Saving debug settings"); + SaveMemory(); + } + } + + /// + /// Callback for console command + /// + /// + /// + /// SetDebugFromConsole method + /// + public static void SetDebugFromConsole(string levelString) + { + try + { + if (levelString.Trim() == "?") + { + CrestronConsole.ConsoleCommandResponse( + "Used to set the minimum level of debug messages:\r\n" + + "Usage: appdebug:P [sink] [level]\r\n" + + " sink: console (default), errorlog, file, all\r\n" + + " all: sets all sinks to the specified level\r\n" + + " level: 0-5 or LogEventLevel name\r\n" + + $"{_logLevels[0]} = 0\r\n" + + $"{_logLevels[1]} = 1\r\n" + + $"{_logLevels[2]} = 2\r\n" + + $"{_logLevels[3]} = 3\r\n" + + $"{_logLevels[4]} = 4\r\n" + + $"{_logLevels[5]} = 5"); + return; + } + + if (string.IsNullOrEmpty(levelString.Trim())) + { + CrestronConsole.ConsoleCommandResponse("Console log level = {0}\r\n", _consoleLogLevelSwitch.MinimumLevel); + CrestronConsole.ConsoleCommandResponse("File log level = {0}\r\n", _fileLogLevelSwitch.MinimumLevel); + CrestronConsole.ConsoleCommandResponse("Error log level = {0}\r\n", _errorLogLevelSwitch.MinimumLevel); + return; + } + + // Parse tokens: first token is sink (defaults to console), second token is level + var tokens = levelString.Trim().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + string sinkName; + string levelToken; + + if (tokens.Length == 1) + { + // Single token - assume it's a level for console sink + sinkName = "console"; + levelToken = tokens[0]; + } + else if (tokens.Length == 2) + { + // Two tokens - first is sink, second is level + sinkName = tokens[0].ToLowerInvariant(); + levelToken = tokens[1]; + } + else + { + CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); + return; + } + + // Parse the level using the same logic as before + LogEventLevel level; + + if (int.TryParse(levelToken, out var levelInt)) + { + if (levelInt < 0 || levelInt > 5) + { + CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level. If using a number, value must be between 0-5"); + return; + } + + if (!_logLevels.TryGetValue((uint)levelInt, out level)) + { + level = LogEventLevel.Information; + CrestronConsole.ConsoleCommandResponse($"{levelInt} not valid. Setting level to {level}"); + } + } + else if (Enum.TryParse(levelToken, true, out level)) + { + // Successfully parsed as LogEventLevel enum + } + else + { + CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level"); + return; + } + + // Set the level for the specified sink + switch (sinkName) + { + case "console": + SetDebugLevel(level); + break; + case "errorlog": + SetErrorLogMinimumDebugLevel(level); + break; + case "file": + SetFileMinimumDebugLevel(level); + break; + case "all": + SetDebugLevel(level); + SetErrorLogMinimumDebugLevel(level); + SetFileMinimumDebugLevel(level); + break; + default: + CrestronConsole.ConsoleCommandResponse($"Error: Unknown sink '{sinkName}'. Valid sinks: console, errorlog, file"); + break; + } + } catch { - try { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); } - catch { /* CrestronConsole unavailable (test/dev env) — swallow to keep type initializer healthy */ } + CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); } } - finally + + /// + /// Sets the debug level + /// + /// Valid values 0-5 + /// + /// SetDebugLevel method + /// + public static void SetDebugLevel(uint level) { - // Guarantee _logger is never null — all Debug.Log* calls are safe even if the - // ctor failed partway through (e.g. on a dev machine without Crestron hardware). - _logger ??= new LoggerConfiguration().MinimumLevel.Fatal().CreateLogger(); - } - } - - /// Creates the WebSocket sink, returning null if construction fails in a test/dev environment. - private static DebugWebsocketSink? TryCreateWebsocketSink() - { - try - { - return new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); - } - catch - { - _console?.PrintLine("DebugWebsocketSink could not be created in this environment; websocket logging disabled."); - return null; - } - } - - private static bool GetDoNotLoadOnNextBoot() - { - if (_dataStore == null) return false; - - if (!_dataStore.TryGetLocalBool(DoNotLoadOnNextBootKey, out var doNotLoad)) - { - LogError("Error retrieving DoNotLoadOnNextBoot value"); - return false; - } - - return doNotLoad; - } - - /// - /// Updates the LoggerConfiguration used by the Debug class. - /// This allows for changing logger settings such as sinks and output templates. - /// After calling this method, the new configuration will be used for all subsequent log messages. - /// - /// - public static void UpdateLoggerConfiguration(LoggerConfiguration config) - { - _loggerConfiguration = config; - - _logger = config.CreateLogger(); - } - - /// - /// Resets the LoggerConfiguration to the default configuration defined in this class. - /// - public static void ResetLoggerConfiguration() - { - _loggerConfiguration = _defaultLoggerConfiguration; - - _logger = _loggerConfiguration.CreateLogger(); - } - - private static LogEventLevel GetStoredLogEventLevel(string levelStoreKey) - { - try - { - if (_dataStore == null) return LogEventLevel.Information; - - if (!_dataStore.TryGetLocalInt(levelStoreKey, out int logLevel)) + if (!_logLevels.TryGetValue(level, out var logLevel)) { - _console?.Print($"Unable to retrieve stored log level for {levelStoreKey}. Setting level to {LogEventLevel.Information}\r\n"); - return LogEventLevel.Information; + logLevel = LogEventLevel.Information; + + CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}"); + + SetDebugLevel(logLevel); } - if (logLevel < 0 || logLevel > 5) - { - _console?.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}"); - return LogEventLevel.Information; - } - - return (LogEventLevel)logLevel; - } - catch (Exception ex) - { - _console?.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); - return LogEventLevel.Information; - } - } - - private static void GetVersion() - { - var assembly = Assembly.GetExecutingAssembly(); - var ver = - assembly - .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); - - if (ver != null && ver.Length > 0) - { - if (ver[0] is AssemblyInformationalVersionAttribute verAttribute) - { - PepperDashCoreVersion = verAttribute.InformationalVersion; - } - } - else - { - var version = assembly.GetName().Version; - PepperDashCoreVersion = version.ToString(); - } - } - - // /// - // /// Used to save memory when shutting down - // /// - // /// - // static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - // { - - // if (programEventType == eProgramStatusEventType.Stopping) - // { - // Log.CloseAndFlush(); - - // if (_saveTimer != null) - // { - // _saveTimer.Stop(); - // _saveTimer = null; - // } - // LogMessage(LogEventLevel.Information, "Saving debug settings"); - // // SaveMemory(); - // } - // } - - /// - /// Callback for console command - /// - /// - public static void SetDebugFromConsole(string levelString) - { - try - { - if (levelString.Trim() == "?") - { - _console?.ConsoleCommandResponse( - "Used to set the minimum level of debug messages:\r\n" + - "Usage: appdebug:P [sink] [level] [timeoutMinutes]\r\n" + - " sink (optional): console (default), errorlog, websocket, file, all\r\n" + - " timeoutMinutes (optional, console sink only): level resets to Information after this many minutes (default 120)\r\n" + - $" level: {string.Join(", ", _logLevels.Values)}"); - return; - } - - if (string.IsNullOrEmpty(levelString.Trim())) - { - _console?.ConsoleCommandResponse($"Console log level = {consoleLoggingLevelSwitch.MinimumLevel}\r\n"); - _console?.ConsoleCommandResponse($"File log level = {fileLoggingLevelSwitch.MinimumLevel}\r\n"); - _console?.ConsoleCommandResponse($"Error log level = {errorLogLevelSwitch.MinimumLevel}\r\n"); - _console?.ConsoleCommandResponse($"Websocket log level = {websocketLoggingLevelSwitch.MinimumLevel}\r\n"); - return; - } - - var tokens = Regex.Split(levelString.Trim(), @"\s+"); - - // Determine whether the first token is a sink name or a level - var knownSinks = new[] { "console", "errorlog", "websocket", "file", "all" }; - string sinkName; - string levelToken; - long timeoutMinutes = defaultConsoleDebugTimeoutMin; - - if (tokens.Length >= 2 && Array.IndexOf(knownSinks, tokens[0].ToLowerInvariant()) >= 0) - { - // First token is a sink name - sinkName = tokens[0].ToLowerInvariant(); - levelToken = tokens[1]; - - if (tokens.Length >= 3 && long.TryParse(tokens[2], out var parsedTimeout)) - timeoutMinutes = Math.Max(parsedTimeout, 1); - } - else - { - // No sink prefix — treat as console with optional timeout as second token - sinkName = "console"; - levelToken = tokens[0]; - - if (tokens.Length >= 2 && long.TryParse(tokens[1], out var parsedTimeout)) - timeoutMinutes = Math.Max(parsedTimeout, 1); - } - - // Parse the level - LogEventLevel level; - - if (!Enum.TryParse(levelToken, true, out level)) - { - _console?.ConsoleCommandResponse($"Error: Unable to parse '{levelToken}' to valid log level. Valid levels: {string.Join(", ", _logLevels.Values)}"); - return; - } - - // Apply to the specified sink(s) - switch (sinkName) - { - case "console": - SetDebugLevel(level, (int)timeoutMinutes); - break; - case "errorlog": - SetErrorLogMinimumDebugLevel(level); - break; - case "websocket": - SetWebSocketMinimumDebugLevel(level); - break; - case "file": - SetFileMinimumDebugLevel(level); - break; - case "all": - SetDebugLevel(level, (int)timeoutMinutes); - SetErrorLogMinimumDebugLevel(level); - SetWebSocketMinimumDebugLevel(level); - SetFileMinimumDebugLevel(level); - break; - default: - _console?.ConsoleCommandResponse($"Error: Unknown sink '{sinkName}'. Valid sinks: console, errorlog, websocket, file, all"); - break; - } - } - catch - { - _console?.ConsoleCommandResponse("Usage: appdebug:P [sink] [level] [timeoutMinutes]"); - } - } - - /// - /// Sets the debug level - /// - /// Valid values 0-5 - /// Timeout in minutes - public static void SetDebugLevel(uint level, int timeout = defaultConsoleDebugTimeoutMin) - { - if (!_logLevels.TryGetValue(level, out var logLevel)) - { - logLevel = LogEventLevel.Information; - - _console?.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}"); - - SetDebugLevel(logLevel, timeout); + SetDebugLevel(logLevel); } - SetDebugLevel(logLevel, timeout); - } - - /// - /// Sets the debug level - /// - /// The log level to set - /// Timeout in minutes - public static void SetDebugLevel(LogEventLevel level, int timeout = defaultConsoleDebugTimeoutMin) - { - consoleDebugTimer.Stop(); - consoleDebugTimer.Interval = timeout * 60000; - consoleDebugTimer.Start(); - - consoleLoggingLevelSwitch.MinimumLevel = level; - - var appNum = _environment?.ApplicationNumber ?? 0; - _console?.ConsoleCommandResponse($"[Application {appNum}], Debug level set to {consoleLoggingLevelSwitch.MinimumLevel}\r\n"); - _console?.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); - - if (_dataStore != null && !_dataStore.SetLocalInt(LevelStoreKey, (int)level)) - _console?.PrintLine($"Error saving console debug level setting"); - else - _console?.ConsoleCommandResponse($"Store result: {(int)level}"); - } - - /// - /// Sets the debug level for the websocket sink - /// - /// - public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) - { - websocketLoggingLevelSwitch.MinimumLevel = level; - - if (_dataStore != null && !_dataStore.SetLocalUint(WebSocketLevelStoreKey, (uint)level)) - LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting"); - - LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel); - } - - - /// - /// Sets the minimum debug level for the error log sink - /// - /// - public static void SetErrorLogMinimumDebugLevel(LogEventLevel level) - { - errorLogLevelSwitch.MinimumLevel = level; - - if (_dataStore != null && !_dataStore.SetLocalUint(ErrorLogLevelStoreKey, (uint)level)) - LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting"); - - LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", errorLogLevelSwitch.MinimumLevel); - } - - /// - /// Sets the minimum debug level for the file sink - /// - public static void SetFileMinimumDebugLevel(LogEventLevel level) - { - fileLoggingLevelSwitch.MinimumLevel = level; - - if (_dataStore != null && !_dataStore.SetLocalUint(FileLevelStoreKey, (uint)level)) - LogMessage(LogEventLevel.Information, "Error saving File debug level setting"); - - LogMessage(LogEventLevel.Information, "File debug level set to {0}", fileLoggingLevelSwitch.MinimumLevel); - } - - /// - /// Callback for console command - /// - /// - public static void SetDoNotLoadOnNextBootFromConsole(string stateString) - { - try + /// + /// SetDebugLevel method + /// + public static void SetDebugLevel(LogEventLevel level) { - if (string.IsNullOrEmpty(stateString.Trim())) - { - _console?.ConsoleCommandResponse($"DoNotLoadOnNextBoot = {DoNotLoadConfigOnNextBoot}"); - return; - } + _consoleLogLevelSwitch.MinimumLevel = level; - SetDoNotLoadConfigOnNextBoot(bool.Parse(stateString)); + CrestronConsole.ConsoleCommandResponse("[Application {0}] Debug level set to {1}\r\n", + InitialParametersClass.ApplicationNumber, _consoleLogLevelSwitch.MinimumLevel); + + CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); + + var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level); + + CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + CrestronConsole.PrintLine($"Error saving console debug level setting: {err}"); } - catch - { - _console?.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]"); - } - } - /// - /// Callback for console command - /// - /// - public static void SetDebugFilterFromConsole(string items) - { - var str = items.Trim(); - if (str == "?") + /// + /// SetWebSocketMinimumDebugLevel method + /// + public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) { - CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + - "+all: at beginning puts filter into 'default include' mode\r" + - " All keys that follow will be excluded from output.\r" + - "-all: at beginning puts filter into 'default exclude all' mode.\r" + - " All keys that follow will be the only keys that are shown\r" + - "+nokey: Enables messages with no key (default)\r" + - "-nokey: Disables messages with no key.\r" + - "(nokey settings are independent of all other settings)"); - return; + _websocketLogLevelSwitch.MinimumLevel = level; + + var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); + + LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLogLevelSwitch.MinimumLevel); } - var keys = Regex.Split(str, @"\s*"); - foreach (var keyToken in keys) + + /// + /// SetErrorLogMinimumDebugLevel method + /// + public static void SetErrorLogMinimumDebugLevel(LogEventLevel level) { - var lkey = keyToken.ToLower(); - if (lkey == "+all") + _errorLogLevelSwitch.MinimumLevel = level; + + CrestronConsole.ConsoleCommandResponse("[Application {0}] Error log level set to {1}\r\n", + InitialParametersClass.ApplicationNumber, _errorLogLevelSwitch.MinimumLevel); + + CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); + + var err = CrestronDataStoreStatic.SetLocalIntValue(ErrorLogLevelStoreKey, (int)level); + + CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + CrestronConsole.PrintLine($"Error saving error log debug level setting: {err}"); + } + + /// + /// SetFileMinimumDebugLevel method + /// + public static void SetFileMinimumDebugLevel(LogEventLevel level) + { + _fileLogLevelSwitch.MinimumLevel = level; + + CrestronConsole.ConsoleCommandResponse("[Application {0}] File log level set to {1}\r\n", + InitialParametersClass.ApplicationNumber, _fileLogLevelSwitch.MinimumLevel); + + CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); + + var err = CrestronDataStoreStatic.SetLocalIntValue(FileLevelStoreKey, (int)level); + + CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + CrestronConsole.PrintLine($"Error saving file debug level setting: {err}"); + } + + /// + /// Callback for console command + /// + /// + /// + /// SetDoNotLoadOnNextBootFromConsole method + /// + public static void SetDoNotLoadOnNextBootFromConsole(string stateString) + { + try { - IncludedExcludedKeys.Clear(); - _excludeAllMode = false; - } - else if (lkey == "-all") - { - IncludedExcludedKeys.Clear(); - _excludeAllMode = true; - } - //else if (lkey == "+nokey") - //{ - // ExcludeNoKeyMessages = false; - //} - //else if (lkey == "-nokey") - //{ - // ExcludeNoKeyMessages = true; - //} - else - { - string key; - if (lkey.StartsWith("-")) + if (string.IsNullOrEmpty(stateString.Trim())) { - key = lkey.Substring(1); - // if in exclude all mode, we need to remove this from the inclusions - if (_excludeAllMode) - { - if (IncludedExcludedKeys.ContainsKey(key)) - IncludedExcludedKeys.Remove(key); - } - // otherwise include all mode, add to the exclusions - else - { - IncludedExcludedKeys[key] = new object(); - } + CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot); + return; } - else if (lkey.StartsWith("+")) - { - key = lkey.Substring(1); - // if in exclude all mode, we need to add this as inclusion - if (_excludeAllMode) - { - IncludedExcludedKeys[key] = new object(); - } - // otherwise include all mode, remove this from exclusions - else + SetDoNotLoadConfigOnNextBoot(bool.Parse(stateString)); + } + catch + { + CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]"); + } + } + + /// + /// Callback for console command + /// + /// + /// + /// SetDebugFilterFromConsole method + /// + public static void SetDebugFilterFromConsole(string items) + { + var str = items.Trim(); + if (str == "?") + { + CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + + "+all: at beginning puts filter into 'default include' mode\r" + + " All keys that follow will be excluded from output.\r" + + "-all: at beginning puts filter into 'default exclude all' mode.\r" + + " All keys that follow will be the only keys that are shown\r" + + "+nokey: Enables messages with no key (default)\r" + + "-nokey: Disables messages with no key.\r" + + "(nokey settings are independent of all other settings)"); + return; + } + var keys = Regex.Split(str, @"\s*"); + foreach (var keyToken in keys) + { + var lkey = keyToken.ToLower(); + if (lkey == "+all") + { + IncludedExcludedKeys.Clear(); + _excludeAllMode = false; + } + else if (lkey == "-all") + { + IncludedExcludedKeys.Clear(); + _excludeAllMode = true; + } + //else if (lkey == "+nokey") + //{ + // ExcludeNoKeyMessages = false; + //} + //else if (lkey == "-nokey") + //{ + // ExcludeNoKeyMessages = true; + //} + else + { + string key; + if (lkey.StartsWith("-")) { - if (IncludedExcludedKeys.ContainsKey(key)) - IncludedExcludedKeys.Remove(key); + key = lkey.Substring(1); + // if in exclude all mode, we need to remove this from the inclusions + if (_excludeAllMode) + { + if (IncludedExcludedKeys.ContainsKey(key)) + IncludedExcludedKeys.Remove(key); + } + // otherwise include all mode, add to the exclusions + else + { + IncludedExcludedKeys[key] = new object(); + } + } + else if (lkey.StartsWith("+")) + { + key = lkey.Substring(1); + // if in exclude all mode, we need to add this as inclusion + if (_excludeAllMode) + { + + IncludedExcludedKeys[key] = new object(); + } + // otherwise include all mode, remove this from exclusions + else + { + if (IncludedExcludedKeys.ContainsKey(key)) + IncludedExcludedKeys.Remove(key); + } } } } } - } - /// - /// sets the settings for a device or creates a new entry - /// - /// - /// - /// - public static void SetDeviceDebugSettings(string deviceKey, object settings) - { - _contexts.SetDebugSettingsForKey(deviceKey, settings); - // SaveMemoryOnTimeout(); - } - - /// - /// Gets the device settings for a device by key or returns null - /// - /// - /// - public static object GetDeviceDebugSettingsForKey(string deviceKey) - { - return _contexts.GetDebugSettingsForKey(deviceKey); - } - - /// - /// Sets the flag to prevent application starting on next boot - /// - /// - public static void SetDoNotLoadConfigOnNextBoot(bool state) - { - DoNotLoadConfigOnNextBoot = state; - - if (_dataStore != null && !_dataStore.SetLocalBool(DoNotLoadOnNextBootKey, state)) - LogError("Error saving DoNotLoadConfigOnNextBoot setting"); - - LogInformation("Do Not Load Config on Next Boot set to {state}", DoNotLoadConfigOnNextBoot); - } - - /// - /// - /// - public static void ShowDebugLog(string s) - { - if (_environment == null) return; // CrestronLogger not available in test environments - var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all"); - foreach (var l in loglist) - _console?.ConsoleCommandResponse(l + CrestronEnvironment.NewLine); - } - - /// - /// Log an Exception as an Error - /// - /// Exception to log - /// Message template - /// Optional IKeyed device. If provided, the Key of the device will be added to the log message - /// Args to put into message template - public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args) - { - using (LogContext.PushProperty("Key", device?.Key)) + /// + /// sets the settings for a device or creates a new entry + /// + /// + /// + /// + public static void SetDeviceDebugSettings(string deviceKey, object settings) { - _logger.Error(ex, message, args); + _contexts.SetDebugSettingsForKey(deviceKey, settings); + SaveMemoryOnTimeout(); } - } - /// - /// Log a message - /// - /// Level to log at - /// Message template - /// Optional IKeyed device. If provided, the Key of the device will be added to the log message - /// Args to put into message template - public static void LogMessage(LogEventLevel level, string message, IKeyed device = null, params object[] args) - { - using (LogContext.PushProperty("Key", device?.Key)) + /// + /// Gets the device settings for a device by key or returns null + /// + /// + /// + /// + /// GetDeviceDebugSettingsForKey method + /// + public static object GetDeviceDebugSettingsForKey(string deviceKey) + { + return _contexts.GetDebugSettingsForKey(deviceKey); + } + + /// + /// Sets the flag to prevent application starting on next boot + /// + /// + public static void SetDoNotLoadConfigOnNextBoot(bool state) + { + DoNotLoadConfigOnNextBoot = state; + _contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state; + SaveMemoryOnTimeout(); + + CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Load Config on Next Boot set to {1}", + InitialParametersClass.ApplicationNumber, DoNotLoadConfigOnNextBoot); + } + + /// + /// ShowDebugLog method + /// + public static void ShowDebugLog(string s) + { + var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all"); + foreach (var l in loglist) + CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine); + } + + /// + /// Log an Exception as an Error + /// + /// Exception to log + /// Message template + /// Optional IKeyed device. If provided, the Key of the device will be added to the log message + /// Args to put into message template + /// + /// LogMessage method + /// + public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args) + { + using (LogContext.PushProperty("Key", device?.Key)) + { + _logger.Error(ex, message, args); + } + } + + /// + /// Log a message + /// + /// Level to log at + /// Message template + /// Optional IKeyed device. If provided, the Key of the device will be added to the log message + /// Args to put into message template + public static void LogMessage(LogEventLevel level, string message, IKeyed device = null, params object[] args) + { + using (LogContext.PushProperty("Key", device?.Key)) + { + _logger.Write(level, message, args); + } + } + + /// + /// Logs a message at the specified log level. + /// + /// Level to log at + /// Message template + /// Args to put into message template + public static void LogMessage(LogEventLevel level, string message, params object[] args) { _logger.Write(level, message, args); } - } - /// - /// Log a message with at the specified level. - /// - public static void LogMessage(LogEventLevel level, string message, params object[] args) - { - _logger.Write(level, message, args); - } - - /// - /// Log a message with at the specified level and exception. - /// - /// - /// - /// - /// - public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) - { - _logger.Write(level, ex, message, args); - } - - /// - /// Log a message with at the specified level and device context. - /// - /// - /// - /// - public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args) - { - LogMessage(level, message, keyed, args); - } - - /// - /// Log a message with at the specified level, exception, and device context. - /// - /// - /// - /// - /// - /// - public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args) - { - using (LogContext.PushProperty("Key", device?.Key)) + /// + /// LogMessage method + /// + public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) { _logger.Write(level, ex, message, args); } - } - #region Explicit methods for logging levels + /// + /// LogMessage method + /// + public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args) + { + LogMessage(level, message, keyed, args); + } - /// - /// Log a message with Verbose level and device context. - /// - /// - /// - /// - public static void LogVerbose(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogMessage method + /// + public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args) + { + using (LogContext.PushProperty("Key", device?.Key)) + { + _logger.Write(level, ex, message, args); + } + } + + #region Explicit methods for logging levels + /// + /// LogVerbose method + /// + public static void LogVerbose(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Verbose, message, args); + } + } + + /// + /// LogVerbose method + /// + public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Verbose, ex, message, args); + } + } + + /// + /// LogVerbose method + /// + public static void LogVerbose(string message, params object[] args) { _logger.Write(LogEventLevel.Verbose, message, args); } - } - /// - /// Log a message with Verbose level, exception, and device context. - /// - public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogVerbose method + /// + public static void LogVerbose(Exception ex, string message, params object[] args) { _logger.Write(LogEventLevel.Verbose, ex, message, args); } - } - /// - /// Log a message with Verbose level. - /// - public static void LogVerbose(string message, params object[] args) - { - _logger.Write(LogEventLevel.Verbose, message, args); - } + /// + /// LogDebug method + /// + public static void LogDebug(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Debug, message, args); + } + } - /// - /// Log a message with Verbose level and exception. - /// - public static void LogVerbose(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Verbose, ex, null, message, args); - } + /// + /// LogDebug method + /// + public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Debug, ex, message, args); + } + } - /// - /// Log a message with Debug level and device context. - /// - public static void LogDebug(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogDebug method + /// + public static void LogDebug(string message, params object[] args) { _logger.Write(LogEventLevel.Debug, message, args); } - } - /// - /// Log a message with Debug level, exception, and device context. - /// - public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogDebug method + /// + public static void LogDebug(Exception ex, string message, params object[] args) { - _logger.Write(LogEventLevel.Debug, ex, message, args); + _logger.Write(LogEventLevel.Debug, ex, null, message, args); } - } - /// - /// Log a message with Debug level. - /// - public static void LogDebug(string message, params object[] args) - { - _logger.Write(LogEventLevel.Debug, message, args); - } + /// + /// LogInformation method + /// + public static void LogInformation(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Information, message, args); + } + } - /// - /// Log a message with Debug level and exception. - /// - public static void LogDebug(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Debug, ex, null, message, args); - } + /// + /// LogInformation method + /// + public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Information, ex, message, args); + } + } - /// - /// Log a message with Information level and device context. - /// - public static void LogInformation(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogInformation method + /// + public static void LogInformation(string message, params object[] args) { _logger.Write(LogEventLevel.Information, message, args); } - } - /// - /// Log a message with Information level, exception, and device context. - /// - public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogInformation method + /// + public static void LogInformation(Exception ex, string message, params object[] args) { _logger.Write(LogEventLevel.Information, ex, message, args); } - } - /// - /// Log a message with Information level. - /// - public static void LogInformation(string message, params object[] args) - { - _logger.Write(LogEventLevel.Information, message, args); - } + /// + /// LogWarning method + /// + public static void LogWarning(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Warning, message, args); + } + } - /// - /// Log a message with Information level and exception. - /// - public static void LogInformation(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Information, ex, null, message, args); - } + /// + /// LogWarning method + /// + public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Warning, ex, message, args); + } + } - /// - /// Log a message with Warning level and device context. - /// - public static void LogWarning(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogWarning method + /// + public static void LogWarning(string message, params object[] args) { _logger.Write(LogEventLevel.Warning, message, args); } - } - /// - /// Log a message with Warning level, exception, and device context. - /// - public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogWarning method + /// + public static void LogWarning(Exception ex, string message, params object[] args) { _logger.Write(LogEventLevel.Warning, ex, message, args); } - } - /// - /// Log a message with Warning level. - /// - public static void LogWarning(string message, params object[] args) - { - _logger.Write(LogEventLevel.Warning, message, args); - } + /// + /// LogError method + /// + public static void LogError(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Error, message, args); + } + } - /// - /// Log a message with Warning level and exception. - /// - public static void LogWarning(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Warning, ex, null, message, args); - } + /// + /// LogError method + /// + public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Error, ex, message, args); + } + } - /// - /// Log a message with Error level and device context. - /// - public static void LogError(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogError method + /// + public static void LogError(string message, params object[] args) { _logger.Write(LogEventLevel.Error, message, args); } - } - /// - /// Log a message with Error level, exception, and device context. - /// - public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogError method + /// + public static void LogError(Exception ex, string message, params object[] args) { _logger.Write(LogEventLevel.Error, ex, message, args); } - } - /// - /// Log a message with Error level. - /// - public static void LogError(string message, params object[] args) - { - _logger.Write(LogEventLevel.Error, message, args); - } + /// + /// LogFatal method + /// + public static void LogFatal(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Fatal, message, args); + } + } - /// - /// Log a message with Error level and exception. - /// - public static void LogError(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Error, ex, null, message, args); - } + /// + /// LogFatal method + /// + public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) + { + _logger.Write(LogEventLevel.Fatal, ex, message, args); + } + } - /// - /// Log a message with Fatal level and device context. - /// - public static void LogFatal(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogFatal method + /// + public static void LogFatal(string message, params object[] args) { _logger.Write(LogEventLevel.Fatal, message, args); } - } - /// - /// Log a message with Fatal level, exception, and device context. - /// - public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) + /// + /// LogFatal method + /// + public static void LogFatal(Exception ex, string message, params object[] args) { _logger.Write(LogEventLevel.Fatal, ex, message, args); } - } - /// - /// Log a message with Fatal level. - /// - /// - /// - public static void LogFatal(string message, params object[] args) - { - _logger.Write(LogEventLevel.Fatal, message, args); - } - - /// - /// Log a message with Fatal level and exception. - /// - public static void LogFatal(Exception ex, string message, params object[] args) - { - _logger.Write(LogEventLevel.Fatal, ex, null, message, args); - } - - #endregion + #endregion - private static void LogMessage(uint level, string format, params object[] items) - { - if (!_logLevels.ContainsKey(level)) return; - - var logLevel = _logLevels[level]; - - LogMessage(logLevel, format, items); - } - - private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items) - { - if (!_logLevels.ContainsKey(level)) return; - - var logLevel = _logLevels[level]; - - LogMessage(logLevel, keyed, format, items); - } - - - // /// - // /// Writes the memory object after timeout - // /// - // static void SaveMemoryOnTimeout() - // { - // LogInformation("Saving debug settings"); - // if (_saveTimer == null) - // { - // _saveTimer = new Timer(SaveTimeoutMs) { AutoReset = false }; - // _saveTimer.Elapsed += (s, e) => - // { - // _saveTimer = null; - // SaveMemory(); - // }; - // _saveTimer.Start(); - // } - // else - // { - // _saveTimer.Stop(); - // _saveTimer.Interval = SaveTimeoutMs; - // _saveTimer.Start(); - // } - // } - - // /// - // /// Writes the memory - use SaveMemoryOnTimeout - // /// - // static void SaveMemory() - // { - // //var dir = @"\NVRAM\debug"; - // //if (!Directory.Exists(dir)) - // // Directory.Create(dir); - - // try - // { - // var fileName = GetMemoryFileName(); - - // LogInformation("Loading debug settings file from {fileName}", fileName); - - // using (var sw = new StreamWriter(fileName)) - // { - // var json = JsonConvert.SerializeObject(_contexts); - // sw.Write(json); - // sw.Flush(); - // } - // } - // catch (Exception ex) - // { - // LogError("Exception saving debug settings: {message}", ex); - // return; - // } - // } - - // /// - // /// - // /// - // static void LoadMemory() - // { - // var file = GetMemoryFileName(); - // if (File.Exists(file)) - // { - // using (var sr = new StreamReader(file)) - // { - // var json = sr.ReadToEnd(); - // _contexts = JsonConvert.DeserializeObject(json); - - // if (_contexts != null) - // { - // LogMessage(LogEventLevel.Debug, "Debug memory restored from file"); - // return; - // } - // } - // } - - // _contexts = new DebugContextCollection(); - // } - - /// - /// Helper to get the file path for this app's debug memory - /// - static string GetMemoryFileName() - { - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + private static void LogMessage(uint level, string format, params object[] items) { - // CheckForMigration(); - return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); + if (!_logLevels.ContainsKey(level)) return; + + var logLevel = _logLevels[level]; + + LogMessage(logLevel, format, items); } - return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); + private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items) + { + if (!_logLevels.ContainsKey(level)) return; + + var logLevel = _logLevels[level]; + + LogMessage(logLevel, keyed, format, items); + } + + + /// + /// Prints message to console if current debug level is equal to or higher than the level of this message. + /// Uses CrestronConsole.PrintLine. + /// + /// + /// Console format string + /// Object parameters + [Obsolete("Use LogMessage methods. Will be removed in 2.2.0 and later versions")] + public static void Console(uint level, string format, params object[] items) + { + + LogMessage(level, format, items); + + //if (IsRunningOnAppliance) + //{ + // CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"), + // InitialParametersClass.ApplicationNumber, + // level, + // string.Format(format, items)); + //} + } + + /// + /// Logs to Console when at-level, and all messages to error log, including device key + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void Console(uint level, IKeyed dev, string format, params object[] items) + { + LogMessage(level, dev, format, items); + + //if (Level >= level) + // Console(level, "[{0}] {1}", dev.Key, message); + } + + /// + /// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log. + /// Uses CrestronConsole.PrintLine. + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, + string format, params object[] items) + { + LogMessage(level, dev, format, items); + } + + /// + /// Logs to Console when at-level, and all messages to error log + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void Console(uint level, ErrorLogLevel errorLogLevel, + string format, params object[] items) + { + LogMessage(level, format, items); + } + + /// + /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at + /// or above the level provided, then the output will be written to both console and the log. Otherwise + /// it will only be written to the log. + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void ConsoleWithLog(uint level, string format, params object[] items) + { + LogMessage(level, format, items); + + // var str = string.Format(format, items); + //if (Level >= level) + // CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str); + // CrestronLogger.WriteToLog(str, level); + } + + /// + /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at + /// or above the level provided, then the output will be written to both console and the log. Otherwise + /// it will only be written to the log. + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items) + { + LogMessage(level, dev, format, items); + + // var str = string.Format(format, items); + // CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level); + } + + /// + /// Prints to log and error log + /// + /// + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + public static void LogError(ErrorLogLevel errorLogLevel, string str) + { + switch (errorLogLevel) + { + case ErrorLogLevel.Error: + LogMessage(LogEventLevel.Error, str); + break; + case ErrorLogLevel.Warning: + LogMessage(LogEventLevel.Warning, str); + break; + case ErrorLogLevel.Notice: + LogMessage(LogEventLevel.Information, str); + break; + } + } + + /// + /// Writes the memory object after timeout + /// + static void SaveMemoryOnTimeout() + { + Console(0, "Saving debug settings"); + if (_saveTimer == null) + _saveTimer = new CTimer(o => + { + _saveTimer = null; + SaveMemory(); + }, SaveTimeoutMs); + else + _saveTimer.Reset(SaveTimeoutMs); + } + + /// + /// Writes the memory - use SaveMemoryOnTimeout + /// + static void SaveMemory() + { + //var dir = @"\NVRAM\debug"; + //if (!Directory.Exists(dir)) + // Directory.Create(dir); + + var fileName = GetMemoryFileName(); + + LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName); + + using (var sw = new StreamWriter(fileName)) + { + var json = JsonConvert.SerializeObject(_contexts); + sw.Write(json); + sw.Flush(); + } + } + + /// + /// + /// + static void LoadMemory() + { + var file = GetMemoryFileName(); + if (File.Exists(file)) + { + using (var sr = new StreamReader(file)) + { + var json = sr.ReadToEnd(); + _contexts = JsonConvert.DeserializeObject(json); + + if (_contexts != null) + { + LogMessage(LogEventLevel.Debug, "Debug memory restored from file"); + return; + } + } + } + + _contexts = new DebugContextCollection(); + } + + /// + /// Helper to get the file path for this app's debug memory + /// + static string GetMemoryFileName() + { + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + // CheckForMigration(); + return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); + } + + return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); + } + + /// + /// Enumeration of ErrorLogLevel values + /// + public enum ErrorLogLevel + { + /// + /// Error + /// + Error, + /// + /// Warning + /// + Warning, + /// + /// Notice + /// + Notice, + /// + /// None + /// + None, + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/Logging/DebugConsoleSink.cs b/src/PepperDash.Core/Logging/DebugConsoleSink.cs index 5e32a8a5..6363caa5 100644 --- a/src/PepperDash.Core/Logging/DebugConsoleSink.cs +++ b/src/PepperDash.Core/Logging/DebugConsoleSink.cs @@ -9,45 +9,56 @@ using System.IO; using System.Text; -namespace PepperDash.Core; - -public class DebugConsoleSink : ILogEventSink +namespace PepperDash.Core { - private readonly ITextFormatter _textFormatter; - - public void Emit(LogEvent logEvent) + /// + /// Represents a DebugConsoleSink + /// + public class DebugConsoleSink : ILogEventSink { - if (!Debug.IsRunningOnAppliance) return; + private readonly ITextFormatter _textFormatter; - /*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}"; - - if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue) + /// + /// Emit method + /// + public void Emit(LogEvent logEvent) { - message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}"; - }*/ + if (!Debug.IsRunningOnAppliance) return; - var buffer = new StringWriter(new StringBuilder(256)); + /*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}"; - _textFormatter.Format(logEvent, buffer); + if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue) + { + message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}"; + }*/ - var message = buffer.ToString(); + var buffer = new StringWriter(new StringBuilder(256)); + + _textFormatter.Format(logEvent, buffer); + + var message = buffer.ToString(); + + CrestronConsole.PrintLine(message); + } + + public DebugConsoleSink(ITextFormatter formatProvider ) + { + _textFormatter = formatProvider ?? new JsonFormatter(); + } - CrestronConsole.PrintLine(message); } - public DebugConsoleSink(ITextFormatter formatProvider ) + public static class DebugConsoleSinkExtensions { - _textFormatter = formatProvider ?? new JsonFormatter(); + /// + /// DebugConsoleSink method + /// + public static LoggerConfiguration DebugConsoleSink( + this LoggerSinkConfiguration loggerConfiguration, + ITextFormatter formatProvider = null) + { + return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider)); + } } } - -public static class DebugConsoleSinkExtensions -{ - public static LoggerConfiguration DebugConsoleSink( - this LoggerSinkConfiguration loggerConfiguration, - ITextFormatter formatProvider = null) - { - return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider)); - } -} diff --git a/src/PepperDash.Core/Logging/DebugContext.cs b/src/PepperDash.Core/Logging/DebugContext.cs new file mode 100644 index 00000000..66e63634 --- /dev/null +++ b/src/PepperDash.Core/Logging/DebugContext.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Newtonsoft.Json; + + +namespace PepperDash.Core +{ + /// + /// Represents a debugging context + /// + [Obsolete("DebugContext is no longer supported and will be removed in a future release.")] + public class DebugContext + { + /// + /// Describes the folder location where a given program stores it's debug level memory. By default, the + /// file written will be named appNdebug where N is 1-10. + /// + public string Key { get; private set; } + + /// + /// The name of the file containing the current debug settings. + /// + //string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber); + + DebugContextSaveData SaveData; + + int SaveTimeoutMs = 30000; + + CTimer SaveTimer; + + + static List Contexts = new List(); + + /// + /// Creates or gets a debug context + /// + /// + /// + /// + /// GetDebugContext method + /// + public static DebugContext GetDebugContext(string key) + { + var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (context == null) + { + context = new DebugContext(key); + Contexts.Add(context); + } + return context; + } + + /// + /// Do not use. For S+ access. + /// + public DebugContext() { } + + DebugContext(string key) + { + Key = key; + if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) + { + // Add command to console + CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", + "appdebug:P [0-2]: Sets the application's console debug message level", + ConsoleAccessLevelEnum.AccessOperator); + } + + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + + LoadMemory(); + } + + /// + /// Used to save memory when shutting down + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + if (SaveTimer != null) + { + SaveTimer.Stop(); + SaveTimer = null; + } + Console(0, "Saving debug settings"); + SaveMemory(); + } + } + + /// + /// Callback for console command + /// + /// + /// + /// SetDebugFromConsole method + /// + public void SetDebugFromConsole(string levelString) + { + try + { + if (string.IsNullOrEmpty(levelString.Trim())) + { + CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level); + return; + } + + SetDebugLevel(Convert.ToInt32(levelString)); + } + catch + { + CrestronConsole.PrintLine("Usage: appdebug:P [0-2]"); + } + } + + /// + /// Sets the debug level + /// + /// Valid values 0 (no debug), 1 (critical), 2 (all messages) + /// + /// SetDebugLevel method + /// + public void SetDebugLevel(int level) + { + if (level <= 2) + { + SaveData.Level = level; + SaveMemoryOnTimeout(); + + CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}", + InitialParametersClass.ApplicationNumber, SaveData.Level); + } + } + + /// + /// Prints message to console if current debug level is equal to or higher than the level of this message. + /// Uses CrestronConsole.PrintLine. + /// + /// + /// Console format string + /// Object parameters + public void Console(uint level, string format, params object[] items) + { + if (SaveData.Level >= level) + CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, + string.Format(format, items)); + } + + /// + /// Console method + /// + public void Console(uint level, IKeyed dev, string format, params object[] items) + { + if (SaveData.Level >= level) + Console(level, "[{0}] {1}", dev.Key, string.Format(format, items)); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public void Console(uint level, IKeyed dev, Debug.ErrorLogLevel errorLogLevel, + string format, params object[] items) + { + if (SaveData.Level >= level) + { + var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items)); + Console(level, str); + LogError(errorLogLevel, str); + } + } + + /// + /// + /// + /// + /// + /// + /// + public void Console(uint level, Debug.ErrorLogLevel errorLogLevel, + string format, params object[] items) + { + if (SaveData.Level >= level) + { + var str = string.Format(format, items); + Console(level, str); + LogError(errorLogLevel, str); + } + } + + /// + /// + /// + /// + /// + /// + /// LogError method + /// + public void LogError(Debug.ErrorLogLevel errorLogLevel, string str) + { + string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str); + switch (errorLogLevel) + { + case Debug.ErrorLogLevel.Error: + ErrorLog.Error(msg); + break; + case Debug.ErrorLogLevel.Warning: + ErrorLog.Warn(msg); + break; + case Debug.ErrorLogLevel.Notice: + ErrorLog.Notice(msg); + break; + } + } + + /// + /// Writes the memory object after timeout + /// + void SaveMemoryOnTimeout() + { + if (SaveTimer == null) + SaveTimer = new CTimer(o => + { + SaveTimer = null; + SaveMemory(); + }, SaveTimeoutMs); + else + SaveTimer.Reset(SaveTimeoutMs); + } + + /// + /// Writes the memory - use SaveMemoryOnTimeout + /// + void SaveMemory() + { + using (StreamWriter sw = new StreamWriter(GetMemoryFileName())) + { + var json = JsonConvert.SerializeObject(SaveData); + sw.Write(json); + sw.Flush(); + } + } + + /// + /// + /// + void LoadMemory() + { + var file = GetMemoryFileName(); + if (File.Exists(file)) + { + using (StreamReader sr = new StreamReader(file)) + { + var data = JsonConvert.DeserializeObject(sr.ReadToEnd()); + if (data != null) + { + SaveData = data; + Debug.Console(1, "Debug memory restored from file"); + return; + } + else + SaveData = new DebugContextSaveData(); + } + } + } + + /// + /// Helper to get the file path for this app's debug memory + /// + string GetMemoryFileName() + { + return string.Format(@"\NVRAM\debugSettings\program{0}-{1}", InitialParametersClass.ApplicationNumber, Key); + } + } + + /// + /// + /// + public class DebugContextSaveData + { + /// + /// + /// + public int Level { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Logging/DebugCrestronLoggerSink.cs b/src/PepperDash.Core/Logging/DebugCrestronLoggerSink.cs index a2e82ec8..76859209 100644 --- a/src/PepperDash.Core/Logging/DebugCrestronLoggerSink.cs +++ b/src/PepperDash.Core/Logging/DebugCrestronLoggerSink.cs @@ -3,26 +3,33 @@ using Crestron.SimplSharp.CrestronLogger; using Serilog.Core; using Serilog.Events; -namespace PepperDash.Core.Logging; - -public class DebugCrestronLoggerSink : ILogEventSink +namespace PepperDash.Core.Logging { - public void Emit(LogEvent logEvent) + /// + /// Represents a DebugCrestronLoggerSink + /// + public class DebugCrestronLoggerSink : ILogEventSink { - if (!Debug.IsRunningOnAppliance) return; - - string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}"; - - if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue) + /// + /// Emit method + /// + public void Emit(LogEvent logEvent) { - message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}"; + if (!Debug.IsRunningOnAppliance) return; + + string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}"; + + if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue) + { + message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}"; + } + + CrestronLogger.WriteToLog(message, (uint)logEvent.Level); } - CrestronLogger.WriteToLog(message, (uint)logEvent.Level); - } - - public DebugCrestronLoggerSink() - { - CrestronLogger.Initialize(1, LoggerModeEnum.RM); + public DebugCrestronLoggerSink() + { + CrestronLogger.Initialize(1, LoggerModeEnum.RM); + } } } diff --git a/src/PepperDash.Core/Logging/DebugErrorLogSink.cs b/src/PepperDash.Core/Logging/DebugErrorLogSink.cs index 3c2e6761..9ab4dc6b 100644 --- a/src/PepperDash.Core/Logging/DebugErrorLogSink.cs +++ b/src/PepperDash.Core/Logging/DebugErrorLogSink.cs @@ -9,56 +9,63 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Core.Logging; - -public class DebugErrorLogSink : ILogEventSink +namespace PepperDash.Core.Logging { - private ITextFormatter _formatter; - - private Dictionary> _errorLogMap = new Dictionary> + /// + /// Represents a DebugErrorLogSink + /// + public class DebugErrorLogSink : ILogEventSink { - { LogEventLevel.Verbose, (msg) => ErrorLog.Notice(msg) }, - {LogEventLevel.Debug, (msg) => ErrorLog.Notice(msg) }, - {LogEventLevel.Information, (msg) => ErrorLog.Notice(msg) }, - {LogEventLevel.Warning, (msg) => ErrorLog.Warn(msg) }, - {LogEventLevel.Error, (msg) => ErrorLog.Error(msg) }, - {LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) } - }; - public void Emit(LogEvent logEvent) - { - string message; + private ITextFormatter _formatter; - if (_formatter == null) + private Dictionary> _errorLogMap = new Dictionary> { - var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance - ? $"App {InitialParametersClass.ApplicationNumber}" - : $"Room {InitialParametersClass.RoomId}"; + { LogEventLevel.Verbose, (msg) => ErrorLog.Notice(msg) }, + {LogEventLevel.Debug, (msg) => ErrorLog.Notice(msg) }, + {LogEventLevel.Information, (msg) => ErrorLog.Notice(msg) }, + {LogEventLevel.Warning, (msg) => ErrorLog.Warn(msg) }, + {LogEventLevel.Error, (msg) => ErrorLog.Error(msg) }, + {LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) } + }; + /// + /// Emit method + /// + public void Emit(LogEvent logEvent) + { + string message; - message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}"; - - if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue) + if (_formatter == null) { - message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}"; + var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance + ? $"App {InitialParametersClass.ApplicationNumber}" + : $"Room {InitialParametersClass.RoomId}"; + + message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}"; + + if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue) + { + message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}"; + } + } else + { + var buffer = new StringWriter(new StringBuilder(256)); + + _formatter.Format(logEvent, buffer); + + message = buffer.ToString(); } - } else - { - var buffer = new StringWriter(new StringBuilder(256)); - _formatter.Format(logEvent, buffer); + if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler)) + { + return; + } - message = buffer.ToString(); + handler(message); } - if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler)) + public DebugErrorLogSink(ITextFormatter formatter = null) { - return; + _formatter = formatter; } - - handler(message); - } - - public DebugErrorLogSink(ITextFormatter formatter = null) - { - _formatter = formatter; } } diff --git a/src/PepperDash.Core/Logging/DebugExtensions.cs b/src/PepperDash.Core/Logging/DebugExtensions.cs index 7ae40d97..ccee4943 100644 --- a/src/PepperDash.Core/Logging/DebugExtensions.cs +++ b/src/PepperDash.Core/Logging/DebugExtensions.cs @@ -2,72 +2,112 @@ using Serilog.Events; using Log = PepperDash.Core.Debug; -namespace PepperDash.Core.Logging; - -public static class DebugExtensions +namespace PepperDash.Core.Logging { - public static void LogException(this IKeyed device, Exception ex, string message, params object[] args) + public static class DebugExtensions { - Log.LogMessage(ex, message, device, args); - } + /// + /// LogException method + /// + public static void LogException(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogMessage(ex, message, device: device, args); + } - public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args); - } + /// + /// LogVerbose method + /// + public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogVerbose(ex, device, message, args); + } - public static void LogVerbose(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Verbose, device, message, args); - } + /// + /// LogVerbose method + /// + public static void LogVerbose(this IKeyed device, string message, params object[] args) + { + Log.LogVerbose(device, message, args); + } - public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Debug, ex, message, device, args); - } + /// + /// LogDebug method + /// + public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogDebug(ex, device, message, args); + } - public static void LogDebug(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Debug, device, message, args); - } + /// + /// LogDebug method + /// + public static void LogDebug(this IKeyed device, string message, params object[] args) + { + Log.LogDebug(device, message, args); + } - public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Information, ex, message, device, args); - } + /// + /// LogInformation method + /// + public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogInformation(ex, device, message, args); + } - public static void LogInformation(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Information, device, message, args); - } + /// + /// LogInformation method + /// + public static void LogInformation(this IKeyed device, string message, params object[] args) + { + Log.LogInformation(device, message, args); + } - public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Warning, ex, message, device, args); - } + /// + /// LogWarning method + /// + public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogWarning(ex, device, message, args); + } - public static void LogWarning(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Warning, device, message, args); - } + /// + /// LogWarning method + /// + public static void LogWarning(this IKeyed device, string message, params object[] args) + { + Log.LogWarning(device, message, args); + } - public static void LogError(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Error, ex, message, device, args); - } + /// + /// LogError method + /// + public static void LogError(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogError(ex, device, message, args); + } - public static void LogError(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Error, device, message, args); - } + /// + /// LogError method + /// + public static void LogError(this IKeyed device, string message, params object[] args) + { + Log.LogError(device, message, args); + } - public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args); - } + /// + /// LogFatal method + /// + public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args) + { + Log.LogFatal(ex, device, message, args); + } - public static void LogFatal(this IKeyed device, string message, params object[] args) - { - Log.LogMessage(LogEventLevel.Fatal, device, message, args); + /// + /// LogFatal method + /// + public static void LogFatal(this IKeyed device, string message, params object[] args) + { + Log.LogFatal(device, message, args); + } } } diff --git a/src/PepperDash.Core/Logging/DebugMemory.cs b/src/PepperDash.Core/Logging/DebugMemory.cs index ebd8c5b3..286707b8 100644 --- a/src/PepperDash.Core/Logging/DebugMemory.cs +++ b/src/PepperDash.Core/Logging/DebugMemory.cs @@ -1,116 +1,127 @@ -extern alias NewtonsoftJson; - -using System.Collections.Generic; -using System.Threading; +using System.Collections.Generic; using Crestron.SimplSharp; -using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; +using Newtonsoft.Json; -namespace PepperDash.Core.Logging; - -/// -/// Class to persist current Debug settings across program restarts -/// -public class DebugContextCollection +namespace PepperDash.Core.Logging { - /// - /// To prevent threading issues with the DeviceDebugSettings collection - /// - private readonly object _deviceDebugSettingsLock = new(); + /// + /// Represents a DebugContextCollection + /// + public class DebugContextCollection + { + /// + /// To prevent threading issues with the DeviceDebugSettings collection + /// + private readonly CCriticalSection _deviceDebugSettingsLock; - [JsonProperty("items")] - private readonly Dictionary _items = new Dictionary(); + [JsonProperty("items")] private readonly Dictionary _items; - /// - /// Collection of the debug settings for each device where the dictionary key is the device key - /// - [JsonProperty("deviceDebugSettings")] - private Dictionary DeviceDebugSettings { get; set; } = new Dictionary(); + /// + /// Collection of the debug settings for each device where the dictionary key is the device key + /// + [JsonProperty("deviceDebugSettings")] + private Dictionary DeviceDebugSettings { get; set; } - /// - /// Default constructor - /// - public DebugContextCollection() - { + /// + /// Default constructor + /// + public DebugContextCollection() + { + _deviceDebugSettingsLock = new CCriticalSection(); + DeviceDebugSettings = new Dictionary(); + _items = new Dictionary(); + } - } + /// + /// Sets the level of a given context item, and adds that item if it does not + /// exist + /// + /// + /// + /// + /// SetLevel method + /// + public void SetLevel(string contextKey, int level) + { + if (level < 0 || level > 2) + return; + GetOrCreateItem(contextKey).Level = level; + } - /// - /// Sets the level of a given context item, and adds that item if it does not - /// exist - /// - /// - /// - /// - /// SetLevel method - /// - public void SetLevel(string contextKey, int level) - { - if (level < 0 || level > 2) - return; - GetOrCreateItem(contextKey).Level = level; - } - - /// - /// Gets a level or creates it if not existing - /// - /// - /// - /// - /// GetOrCreateItem method - /// - public DebugContextItem GetOrCreateItem(string contextKey) - { - if (!_items.ContainsKey(contextKey)) - _items[contextKey] = new DebugContextItem { Level = 0 }; - return _items[contextKey]; - } + /// + /// Gets a level or creates it if not existing + /// + /// + /// + /// + /// GetOrCreateItem method + /// + public DebugContextItem GetOrCreateItem(string contextKey) + { + if (!_items.ContainsKey(contextKey)) + _items[contextKey] = new DebugContextItem { Level = 0 }; + return _items[contextKey]; + } - /// - /// sets the settings for a device or creates a new entry - /// - /// - /// - /// - public void SetDebugSettingsForKey(string deviceKey, object settings) - { - lock (_deviceDebugSettingsLock) + /// + /// sets the settings for a device or creates a new entry + /// + /// + /// + /// + /// + /// SetDebugSettingsForKey method + /// + public void SetDebugSettingsForKey(string deviceKey, object settings) { - if (DeviceDebugSettings.ContainsKey(deviceKey)) + try { - DeviceDebugSettings[deviceKey] = settings; + _deviceDebugSettingsLock.Enter(); + + if (DeviceDebugSettings.ContainsKey(deviceKey)) + { + DeviceDebugSettings[deviceKey] = settings; + } + else + DeviceDebugSettings.Add(deviceKey, settings); + } + finally + { + _deviceDebugSettingsLock.Leave(); } - else - DeviceDebugSettings.Add(deviceKey, settings); } - } - /// - /// Gets the device settings for a device by key or returns null - /// - /// - /// - public object GetDebugSettingsForKey(string deviceKey) - { - return DeviceDebugSettings[deviceKey]; - } -} + /// + /// Gets the device settings for a device by key or returns null + /// + /// + /// + /// + /// GetDebugSettingsForKey method + /// + public object GetDebugSettingsForKey(string deviceKey) + { + return DeviceDebugSettings[deviceKey]; + } + } -/// -/// Contains information about -/// -public class DebugContextItem -{ - /// - /// The level of debug messages to print - /// - [JsonProperty("level")] - public int Level { get; set; } + /// + /// Contains information about + /// + public class DebugContextItem + { + /// + /// The level of debug messages to print + /// + [JsonProperty("level")] + public int Level { get; set; } - /// - /// Property to tell the program not to intitialize when it boots, if desired - /// - [JsonProperty("doNotLoadOnNextBoot")] - public bool DoNotLoadOnNextBoot { get; set; } + /// + /// Property to tell the program not to intitialize when it boots, if desired + /// + [JsonProperty("doNotLoadOnNextBoot")] + public bool DoNotLoadOnNextBoot { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs index 07e8f489..cfbf5785 100644 --- a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs +++ b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs @@ -1,461 +1,479 @@ -extern alias NewtonsoftJson; - -using System; -using System.Security.Authentication; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Crestron.SimplSharp; +using System; using Serilog; -using Serilog.Configuration; using Serilog.Core; using Serilog.Events; -using Serilog.Formatting; -using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; -using Serilog.Formatting.Json; -using System.IO; -using WebSocketSharp; +using Serilog.Configuration; using WebSocketSharp.Server; +using Crestron.SimplSharp; +using WebSocketSharp; +using System.Security.Authentication; using WebSocketSharp.Net; +using System.Security.Cryptography.X509Certificates; +using System.IO; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; +using Serilog.Formatting; +using Serilog.Formatting.Json; -namespace PepperDash.Core; - -/// -/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected -/// WebSocket clients. -/// -/// This class implements the interface and is designed to send -/// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally -/// and uses a self-signed certificate for SSL/TLS encryption. -public class DebugWebsocketSink : ILogEventSink, IKeyed +namespace PepperDash.Core { - private HttpServer _httpsServer; - - private readonly string _path = "/debug/join/"; - private const string _certificateName = "selfCres"; - private const string _certificatePassword = "cres12345"; - private static string CertPath => - $"{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}{_certificateName}.pfx"; - /// - /// Gets the port number on which the HTTPS server is currently running. + /// Represents a DebugWebsocketSink /// - public int Port + public class DebugWebsocketSink : ILogEventSink { - get + private HttpServer _httpsServer; + + private string _path = "/debug/join/"; + private const string _certificateName = "selfCres"; + private const string _certificatePassword = "cres12345"; + + private static string CertPath => + $"{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}{_certificateName}.pfx"; + + + public int Port { - - if (_httpsServer == null) return 0; - return _httpsServer.Port; - } - } - - /// - /// Gets the WebSocket URL for the current server instance. - /// - /// The URL is dynamically constructed based on the server's current IP address, port, - /// and WebSocket path. - public string Url - { - get - { - if (_httpsServer == null || !_httpsServer.IsListening) return ""; - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return ""; - - // Use CSLAN IP if available, otherwise fallback to primary IP. This ensures we provide a reachable URL in dual-stack environments. - if (!string.IsNullOrEmpty(CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 1))) - return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 1)}:{_httpsServer.Port}{service.Path}"; - else - return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{service.Path}"; - } - } - - /// - /// Gets a value indicating whether the HTTPS server is currently listening for incoming connections. - /// - public bool IsRunning { get => _httpsServer?.IsListening ?? false; } - - /// - /// Gets a value indicating whether there are active WebSocket connections. - /// - public bool HasActiveConnections - { - get - { - if (_httpsServer == null || !_httpsServer.IsListening) return false; - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return false; - return service.Sessions.Count > 0; - } - } - - /// - public string Key => "DebugWebsocketSink"; - - private readonly ITextFormatter _textFormatter; - - /// - /// Initializes a new instance of the class with the specified text formatter. - /// - /// This constructor initializes the WebSocket sink and ensures that a certificate is - /// available for secure communication. If the required certificate does not exist, it will be created - /// automatically. Additionally, the sink is configured to stop the server when the program is - /// stopping. - /// The text formatter used to format log messages. If null, a default JSON formatter is used. - public DebugWebsocketSink(ITextFormatter formatProvider) - { - - _textFormatter = formatProvider ?? new JsonFormatter(); - - if (!File.Exists(CertPath)) - CreateCert(); - - try - { - CrestronEnvironment.ProgramStatusEventHandler += type => + get { - if (type == eProgramStatusEventType.Stopping) - StopServer(); - }; - } - catch - { - // CrestronEnvironment is not available in test / dev environments — safe to skip. - } - } - private static void CreateCert() - { - // NOTE: This method is called from the constructor, which is itself called during Debug's static - // constructor before _logger is assigned. Do NOT call any Debug.Log* methods here — use - // CrestronConsole.PrintLine only, to avoid a NullReferenceException that would poison the Debug type. - try - { - var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); - var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0); - - CrestronConsole.PrintLine(string.Format("CreateCert: DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress)); - - var subjectName = string.Format("CN={0}.{1}", hostName, domainName); - var fqdn = string.Format("{0}.{1}", hostName, domainName); - - using var rsa = RSA.Create(2048); - - var request = new CertificateRequest( - subjectName, - rsa, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - // Subject Key Identifier - request.CertificateExtensions.Add( - new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); - - // Extended Key Usage: server + client auth - request.CertificateExtensions.Add( - new X509EnhancedKeyUsageExtension( - new OidCollection - { - new Oid("1.3.6.1.5.5.7.3.1"), // id-kp-serverAuth - new Oid("1.3.6.1.5.5.7.3.2") // id-kp-clientAuth - }, - false)); - - // Subject Alternative Names: DNS + IP - var sanBuilder = new SubjectAlternativeNameBuilder(); - sanBuilder.AddDnsName(fqdn); - if (System.Net.IPAddress.TryParse(ipAddress, out var ip)) - sanBuilder.AddIpAddress(ip); - request.CertificateExtensions.Add(sanBuilder.Build()); - - var notBefore = DateTimeOffset.UtcNow; - var notAfter = notBefore.AddYears(2); - - using var cert = request.CreateSelfSigned(notBefore, notAfter); - - var separator = Path.DirectorySeparatorChar; - var outputPath = string.Format("{0}user{1}{2}.pfx", separator, separator, _certificateName); - - var pfxBytes = cert.Export(X509ContentType.Pfx, _certificatePassword); - File.WriteAllBytes(outputPath, pfxBytes); - - CrestronConsole.PrintLine(string.Format("CreateCert: Certificate written to {0}", outputPath)); - } - catch (Exception ex) - { - CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed: {0}\r\n{1}", ex.Message, ex.StackTrace)); - } - } - - /// - /// Sends a log event to all connected WebSocket clients. - /// - /// The log event is formatted using the configured text formatter and then broadcasted - /// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not - /// listening, the method exits without performing any action. - /// The log event to be formatted and broadcasted. Cannot be null. - public void Emit(LogEvent logEvent) - { - if (_httpsServer == null || !_httpsServer.IsListening) return; - - var sw = new StringWriter(); - _textFormatter.Format(logEvent, sw); - - _httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString()); - } - - /// - /// Starts the WebSocket server on the specified port and configures it with the appropriate certificate. - /// - /// This method initializes the WebSocket server and binds it to the specified port. It - /// also applies the server's certificate for secure communication. Ensure that the port is not already in use - /// and that the certificate file is accessible. - /// The port number on which the WebSocket server will listen. Must be a valid, non-negative port number. - public void StartServerAndSetPort(int port) - { - Debug.LogInformation("Starting Websocket Server on port: {0}", port); - - - Start(port, CertPath, _certificatePassword); - } - - private static X509Certificate2 LoadOrRecreateCert(string certPath, string certPassword) - { - try - { - // EphemeralKeySet is required on Linux/OpenSSL (Crestron 4-series) to avoid - // key-container persistence failures, and avoids the private key export restriction. - return new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.EphemeralKeySet); - } - catch (Exception ex) - { - // Cert is stale or was generated by an incompatible library (e.g. old BouncyCastle output). - // Delete it, regenerate with the BCL path, and retry once. - CrestronConsole.PrintLine(string.Format("SSL cert load failed ({0}); regenerating...", ex.Message)); - try { File.Delete(certPath); } catch { } - CreateCert(); - return new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.EphemeralKeySet); - } - } - - private void Start(int port, string certPath = "", string certPassword = "") - { - try - { - _httpsServer = new HttpServer(port, true); - - if (!string.IsNullOrWhiteSpace(certPath)) - { - Debug.LogInformation("Assigning SSL Configuration"); - - _httpsServer.SslConfiguration.ServerCertificate = LoadOrRecreateCert(certPath, certPassword); - _httpsServer.SslConfiguration.ClientCertificateRequired = false; - _httpsServer.SslConfiguration.CheckCertificateRevocation = false; - _httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; - //this is just to test, you might want to actually validate - _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => - { - Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered"); - return true; - }; + if (_httpsServer == null) return 0; + return _httpsServer.Port; } - Debug.LogInformation("Adding Debug Client Service"); - _httpsServer.AddWebSocketService(_path); - _httpsServer.OnGet += HandleHttpGet; - Debug.LogInformation("Assigning Log Info"); - _httpsServer.Log.Level = LogLevel.Trace; - _httpsServer.Log.Output = WriteWebSocketInternalLog; - Debug.LogInformation("Starting"); - - _httpsServer.Start(); - Debug.LogInformation("Ready"); } - catch (Exception ex) + + public string Url { - Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message); - Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace); - // Null out the server so callers can detect failure via IsRunning / Url null guards. - _httpsServer = null; + get + { + if (_httpsServer == null || !_httpsServer.IsListening) return ""; + var service = _httpsServer.WebSocketServices[_path]; + if (service == null) return ""; + + // Use CSLAN IP if available, otherwise fallback to primary IP. This ensures we provide a reachable URL in dual-stack environments. + var cslanIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 1); + if (!string.IsNullOrEmpty(cslanIp) && cslanIp != "Invalid Value") + return $"wss://{cslanIp}:{_httpsServer.Port}{service.Path}"; + else + return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{service.Path}"; + } } - } - private void HandleHttpGet(object sender, HttpRequestEventArgs e) - { - var res = e.Response; - var body = System.Text.Encoding.UTF8.GetBytes( - "

Certificate accepted.

You can close this tab and return to the application.

"); - res.ContentType = "text/html"; - res.ContentLength64 = body.Length; - res.Close(body, true); - } + /// + /// Gets or sets the IsRunning + /// + public bool IsRunning { get => _httpsServer?.IsListening ?? false; } - /// - /// Stops the WebSocket server if it is currently running. - /// - /// This method halts the WebSocket server and releases any associated resources. After - /// calling this method, the server will no longer accept or process incoming connections. - public void StopServer() - { - Debug.LogInformation("Stopping Websocket Server"); - - try + /// + /// Gets a value indicating whether there are active WebSocket connections. + /// + public bool HasActiveConnections { - if (_httpsServer == null || !_httpsServer.IsListening) + get { - return; + if (_httpsServer == null || !_httpsServer.IsListening) return false; + var service = _httpsServer.WebSocketServices[_path]; + if (service == null) return false; + return service.Sessions.Count > 0; } + } - // Prevent close-sequence internal websocket logs from re-entering the logging pipeline. - _httpsServer.Log.Output = (d, s) => { }; - var serviceHost = _httpsServer.WebSocketServices[_path]; + private readonly ITextFormatter _textFormatter; - if (serviceHost == null) + /// + /// Initializes a new instance of the class with the specified text formatter. + /// + /// This constructor initializes the WebSocket sink and ensures that a certificate is + /// available for secure communication. If the required certificate does not exist, it will be created + /// automatically. Additionally, the sink is configured to stop the server when the program is + /// stopping. + /// The text formatter used to format log messages. If null, a default JSON formatter is used. + public DebugWebsocketSink(ITextFormatter formatProvider) + { + + _textFormatter = formatProvider ?? new JsonFormatter(); + + if (!File.Exists(CertPath)) + CreateCert(); + + try { - _httpsServer.Stop(); - _httpsServer = null; - return; - } - - serviceHost.Sessions.Broadcast("Server is stopping"); - - foreach (var session in serviceHost.Sessions.Sessions) - { - if (session?.Context?.WebSocket != null && session.Context.WebSocket.IsAlive) + CrestronEnvironment.ProgramStatusEventHandler += type => { - session.Context.WebSocket.Close(1001, "Server is stopping"); + if (type == eProgramStatusEventType.Stopping) + StopServer(); + }; + } + catch + { + // CrestronEnvironment is not available in test / dev environments — safe to skip. + } + } + + private static void CreateCert() + { + // NOTE: This method is called from the constructor, which is itself called during Debug's static + // constructor before _logger is assigned. Do NOT call any Debug.Log* methods here — use + // CrestronConsole.PrintLine only, to avoid a NullReferenceException that would poison the Debug type. + try + { + var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0); + + CrestronConsole.PrintLine(string.Format("CreateCert: DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress)); + + var subjectName = string.Format("CN={0}.{1}", hostName, domainName); + var fqdn = string.Format("{0}.{1}", hostName, domainName); + + var random = new SecureRandom(); + + // Generate RSA 2048 key pair + var keyPairGenerator = new RsaKeyPairGenerator(); + keyPairGenerator.Init(new KeyGenerationParameters(random, 2048)); + var keyPair = keyPairGenerator.GenerateKeyPair(); + + // Build certificate + var certGenerator = new X509V3CertificateGenerator(); + certGenerator.SetSerialNumber(BigInteger.ValueOf(Math.Abs(DateTime.UtcNow.Ticks))); + certGenerator.SetIssuerDN(new X509Name(subjectName)); + certGenerator.SetSubjectDN(new X509Name(subjectName)); + certGenerator.SetNotBefore(DateTime.UtcNow); + certGenerator.SetNotAfter(DateTime.UtcNow.AddYears(2)); + certGenerator.SetPublicKey(keyPair.Public); + + // Extended Key Usage: server + client auth + certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, + new ExtendedKeyUsage(new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth })); + + // Subject Alternative Names: DNS + IP + System.Net.IPAddress parsedIp; + if (System.Net.IPAddress.TryParse(ipAddress, out parsedIp)) + { + certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, + new GeneralNames(new GeneralName[] { + new GeneralName(GeneralName.DnsName, fqdn), + new GeneralName(GeneralName.IPAddress, ipAddress) + })); + } + else + { + certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, + new GeneralNames(new GeneralName(GeneralName.DnsName, fqdn))); + } + + // Sign with SHA256withRSA + var signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", keyPair.Private, random); + var certificate = certGenerator.Generate(signatureFactory); + + // Export as PKCS12/PFX + var pkcs12Store = new Pkcs12StoreBuilder().Build(); + var certEntry = new X509CertificateEntry(certificate); + pkcs12Store.SetCertificateEntry(_certificateName, certEntry); + pkcs12Store.SetKeyEntry(_certificateName, new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry }); + + var separator = Path.DirectorySeparatorChar; + var outputPath = string.Format("{0}user{1}{2}.pfx", separator, separator, _certificateName); + + using (var ms = new MemoryStream()) + { + var passwordChars = _certificatePassword.ToCharArray(); + try + { + pkcs12Store.Save(ms, passwordChars, random); + } + finally + { + Array.Clear(passwordChars, 0, passwordChars.Length); + } + File.WriteAllBytes(outputPath, ms.ToArray()); + } + + CrestronConsole.PrintLine(string.Format("CreateCert: Certificate written to {0}", outputPath)); + } + catch (Exception ex) + { + CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed: {0}\r\n{1}", ex.Message, ex.StackTrace)); + } + } + + /// + /// Emit method + /// + public void Emit(LogEvent logEvent) + { + if (_httpsServer == null || !_httpsServer.IsListening) return; + + var sw = new StringWriter(); + _textFormatter.Format(logEvent, sw); + + _httpsServer.WebSocketServices.Broadcast(sw.ToString()); + + } + + /// + /// Starts the WebSocket server on the specified port and configures it with the appropriate certificate. + /// + /// This method initializes the WebSocket server and binds it to the specified port. It + /// also applies the server's certificate for secure communication. Ensure that the port is not already in use + /// and that the certificate file is accessible. + /// The port number on which the WebSocket server will listen. Must be a valid, non-negative port number. + public void StartServerAndSetPort(int port) + { + Debug.LogInformation("Starting Websocket Server on port: {0}", port); + + + + + Start(port, CertPath, _certificatePassword); + } + + private static X509Certificate2 LoadOrRecreateCert(string certPath, string certPassword) + { + if (!File.Exists(certPath)) + CreateCert(); + + try + { + return LoadCertFromBouncyCastle(certPath, certPassword); + } + catch (Exception ex) + { + // Cert is corrupt or was written by an incompatible tool — delete and regenerate once. + CrestronConsole.PrintLine(string.Format("SSL cert load failed ({0}); regenerating...", ex.Message)); + try { File.Delete(certPath); } catch { } + CreateCert(); + return LoadCertFromBouncyCastle(certPath, certPassword); + } + } + + /// + /// Loads a PKCS#12 file written by BouncyCastle and returns an with + /// private key attached. + /// The PFX is parsed and re-encoded by BouncyCastle (ensuring format compatibility), then passed as + /// raw bytes to so neither RSACryptoServiceProvider nor the + /// EphemeralKeySet flag (unsupported on the Crestron/Mono runtime) is needed. + /// + private static X509Certificate2 LoadCertFromBouncyCastle(string certPath, string certPassword) + { + var passwordChars = certPassword.ToCharArray(); + try + { + using (var stream = File.OpenRead(certPath)) + { + var store = new Pkcs12StoreBuilder().Build(); + store.Load(stream, passwordChars); + + // Re-encode through BouncyCastle to guarantee PKCS#12 format compatibility, + // then hand raw bytes to X509Certificate2 — no RSACryptoServiceProvider needed. + using (var ms = new MemoryStream()) + { + store.Save(ms, passwordChars, new SecureRandom()); + var cert = new X509Certificate2(ms.ToArray(), certPassword); + + if (!cert.HasPrivateKey) + throw new InvalidOperationException( + string.Format("Certificate loaded from '{0}' does not contain a private key and cannot be used as a server certificate.", certPath)); + + return cert; + } } } - - _httpsServer.Stop(); - - _httpsServer = null; - - } - catch (Exception ex) - { - Debug.LogError(ex, "WebSocket Failed to stop gracefully {0}", ex.Message); - Debug.LogVerbose("Stack Trace\r\n{0}", ex.StackTrace); - } - } - - private static void WriteWebSocketInternalLog(LogData data, string supplemental) - { - try - { - if (data == null) + finally { - return; + Array.Clear(passwordChars, 0, passwordChars.Length); } - - var message = string.IsNullOrWhiteSpace(data.Message) ? "" : data.Message; - var details = string.IsNullOrWhiteSpace(supplemental) ? string.Empty : string.Format(" | details: {0}", supplemental); - - // Use direct console output to avoid recursive log sink calls. - CrestronConsole.PrintLine(string.Format("WS[{0}] {1} | message: {2}{3}", data.Level, data.Date, message, details)); } - catch + + private void Start(int port, string certPath = "", string certPassword = "") { - // Never throw from websocket log callback. - } - } - -} - -/// -/// Configures the logger to write log events to a debug WebSocket sink. -/// -/// This extension method allows you to direct log events to a WebSocket sink for debugging -/// purposes. -public static class DebugWebsocketSinkExtensions -{ - /// - /// Configures a logger to write log events to a debug WebSocket sink. - /// - /// This method adds a sink that writes log events to a WebSocket for debugging purposes. - /// It is typically used during development to stream log events in real-time. - /// The logger sink configuration to apply the WebSocket sink to. - /// An optional text formatter to format the log events. If not provided, a default formatter will be used. - /// A object that can be used to further configure the logger. - public static LoggerConfiguration DebugWebsocketSink( - this LoggerSinkConfiguration loggerConfiguration, - ITextFormatter formatProvider = null) - { - return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider)); - } -} - -/// -/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message -/// handling functionality. -/// -/// The class extends to handle -/// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the -/// duration of the connection and logs relevant events for debugging. -public class DebugClient : WebSocketBehavior -{ - private DateTime _connectionTime; - - /// - /// Gets the duration of time the WebSocket connection has been active. - /// - public TimeSpan ConnectedDuration - { - get - { - if (Context.WebSocket.IsAlive) + try { - return DateTime.Now - _connectionTime; + _httpsServer = new HttpServer(port, true); + + if (!string.IsNullOrWhiteSpace(certPath)) + { + Debug.LogInformation("Assigning SSL Configuration"); + + _httpsServer.SslConfiguration.ServerCertificate = LoadOrRecreateCert(certPath, certPassword); + _httpsServer.SslConfiguration.ClientCertificateRequired = false; + _httpsServer.SslConfiguration.CheckCertificateRevocation = false; + _httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; + //this is just to test, you might want to actually validate + _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => + { + Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered"); + return true; + }; + } + Debug.LogInformation("Adding Debug Client Service"); + _httpsServer.AddWebSocketService(_path); + Debug.LogInformation("Assigning Log Info"); + _httpsServer.Log.Level = LogLevel.Trace; + _httpsServer.Log.Output = WriteWebSocketInternalLog; + Debug.LogInformation("Starting"); + + _httpsServer.Start(); + Debug.LogInformation("Ready"); } - else + catch (Exception ex) { - return new TimeSpan(0); + Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message); + Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace); + // Null out the server so callers can detect failure via IsRunning / Url null guards. + _httpsServer = null; + } + } + + /// + /// StopServer method + /// + public void StopServer() + { + Debug.LogInformation("Stopping Websocket Server"); + + try + { + if (_httpsServer == null || !_httpsServer.IsListening) + { + return; + } + + // Prevent close-sequence internal websocket logs from re-entering the logging pipeline. + _httpsServer.Log.Output = (d, s) => { }; + + var serviceHost = _httpsServer.WebSocketServices[_path]; + + if (serviceHost == null) + { + _httpsServer.Stop(); + _httpsServer = null; + return; + } + + serviceHost.Sessions.Broadcast("Server is stopping"); + + foreach (var session in serviceHost.Sessions.Sessions) + { + if (session?.Context?.WebSocket != null && session.Context.WebSocket.IsAlive) + { + session.Context.WebSocket.Close(1001, "Server is stopping"); + } + } + + _httpsServer.Stop(); + + _httpsServer = null; + + } + catch (Exception ex) + { + Debug.LogError(ex, "WebSocket Failed to stop gracefully {0}", ex.Message); + Debug.LogVerbose("Stack Trace\r\n{0}", ex.StackTrace); + } + } + + private static void WriteWebSocketInternalLog(LogData data, string supplemental) + { + try + { + if (data == null) + { + return; + } + + var message = string.IsNullOrWhiteSpace(data.Message) ? "" : data.Message; + var details = string.IsNullOrWhiteSpace(supplemental) ? string.Empty : string.Format(" | details: {0}", supplemental); + + // Use direct console output to avoid recursive log sink calls. + CrestronConsole.PrintLine(string.Format("WS[{0}] {1} | message: {2}{3}", data.Level, data.Date, message, details)); + } + catch + { + // Never throw from websocket log callback. } } } + public static class DebugWebsocketSinkExtensions + { + /// + /// DebugWebsocketSink method + /// + public static LoggerConfiguration DebugWebsocketSink( + this LoggerSinkConfiguration loggerConfiguration, + ITextFormatter formatProvider = null) + { + return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider)); + } + } + /// - /// Initializes a new instance of the class. + /// Represents a DebugClient /// - public DebugClient() + public class DebugClient : WebSocketBehavior { - Debug.LogInformation("DebugClient Created"); - } + private DateTime _connectionTime; - /// - protected override void OnOpen() - { - base.OnOpen(); + public TimeSpan ConnectedDuration + { + get + { + if (Context.WebSocket.IsAlive) + { + return DateTime.Now - _connectionTime; + } + else + { + return new TimeSpan(0); + } + } + } - var url = Context.WebSocket.Url; - Debug.LogInformation("New WebSocket Connection from: {0}", url); + public DebugClient() + { + Debug.Console(0, "DebugClient Created"); + } - _connectionTime = DateTime.Now; - } + protected override void OnOpen() + { + base.OnOpen(); - /// - protected override void OnMessage(MessageEventArgs e) - { - base.OnMessage(e); + var url = Context.WebSocket.Url; + Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url); - Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data); - } + _connectionTime = DateTime.Now; + } - /// - protected override void OnClose(CloseEventArgs e) - { - base.OnClose(e); + protected override void OnMessage(MessageEventArgs e) + { + base.OnMessage(e); - Debug.LogDebug("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); - } + Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data); + } - /// - protected override void OnError(WebSocketSharp.ErrorEventArgs e) - { - base.OnError(e); + protected override void OnClose(CloseEventArgs e) + { + base.OnClose(e); - Debug.LogError(e.Exception, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); - Debug.LogVerbose("Stack Trace:\r{0}", e.Exception.StackTrace); + Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); + + } + + protected override void OnError(WebSocketSharp.ErrorEventArgs e) + { + base.OnError(e); + + Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); + } } } diff --git a/src/PepperDash.Core/Network/DiscoveryThings.cs b/src/PepperDash.Core/Network/DiscoveryThings.cs index c01613b7..973c03a4 100644 --- a/src/PepperDash.Core/Network/DiscoveryThings.cs +++ b/src/PepperDash.Core/Network/DiscoveryThings.cs @@ -4,17 +4,19 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core; - -/// -/// Not in use -/// - public static class NetworkComm - { +namespace PepperDash.Core +{ /// /// Not in use /// + public static class NetworkComm + { + /// + /// Not in use + /// static NetworkComm() { } - } \ No newline at end of file + } + +} \ No newline at end of file diff --git a/src/PepperDash.Core/PasswordManagement/Config.cs b/src/PepperDash.Core/PasswordManagement/Config.cs index a5f071a4..22aa4881 100644 --- a/src/PepperDash.Core/PasswordManagement/Config.cs +++ b/src/PepperDash.Core/PasswordManagement/Config.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.PasswordManagement; - +namespace PepperDash.Core.PasswordManagement +{ /// /// JSON password configuration /// @@ -22,4 +22,5 @@ namespace PepperDash.Core.PasswordManagement; { } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/PasswordManagement/Constants.cs b/src/PepperDash.Core/PasswordManagement/Constants.cs index d4cf1e0b..65a1bf45 100644 --- a/src/PepperDash.Core/PasswordManagement/Constants.cs +++ b/src/PepperDash.Core/PasswordManagement/Constants.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.PasswordManagement; - +namespace PepperDash.Core.PasswordManagement +{ /// /// Constants /// @@ -53,4 +53,5 @@ namespace PepperDash.Core.PasswordManagement; /// Generic string value change constant ///
public const ushort StringValueChange = 201; - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/PasswordManagement/PasswordClient.cs b/src/PepperDash.Core/PasswordManagement/PasswordClient.cs index 4a6c7368..9443be0b 100644 --- a/src/PepperDash.Core/PasswordManagement/PasswordClient.cs +++ b/src/PepperDash.Core/PasswordManagement/PasswordClient.cs @@ -1,10 +1,10 @@ using System; -namespace PepperDash.Core.PasswordManagement; - -/// -/// A class to allow user interaction with the PasswordManager -/// +namespace PepperDash.Core.PasswordManagement +{ + /// + /// Represents a PasswordClient + /// public class PasswordClient { /// @@ -192,4 +192,5 @@ namespace PepperDash.Core.PasswordManagement; GetPasswordByIndex(args.Index); } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/PasswordManagement/PasswordManager.cs b/src/PepperDash.Core/PasswordManagement/PasswordManager.cs index a1c869a8..9ba008e4 100644 --- a/src/PepperDash.Core/PasswordManagement/PasswordManager.cs +++ b/src/PepperDash.Core/PasswordManagement/PasswordManager.cs @@ -1,252 +1,247 @@ using System; using System.Collections.Generic; -using System.Timers; +using Crestron.SimplSharp; -namespace PepperDash.Core.PasswordManagement; - -/// -/// Allows passwords to be stored and managed -/// -public class PasswordManager +namespace PepperDash.Core.PasswordManagement { - /// - /// Public dictionary of known passwords - /// - public static Dictionary Passwords = new Dictionary(); - /// - /// Private dictionary, used when passwords are updated - /// - private Dictionary _passwords = new Dictionary(); - - /// - /// Timer used to wait until password changes have stopped before updating the dictionary - /// - Timer PasswordTimer; - /// - /// Timer length - /// - public long PasswordTimerElapsedMs = 5000; - - /// - /// Boolean event - /// - public event EventHandler BoolChange; - /// - /// Ushort event - /// - public event EventHandler UshrtChange; - /// - /// String event - /// - public event EventHandler StringChange; - /// - /// Event to notify clients of an updated password at the specified index (uint) - /// - public static event EventHandler PasswordChange; - - /// - /// Constructor - /// - public PasswordManager() + /// + /// Represents a PasswordManager + /// + public class PasswordManager { + /// + /// Public dictionary of known passwords + /// + public static Dictionary Passwords = new Dictionary(); + /// + /// Private dictionary, used when passwords are updated + /// + private Dictionary _passwords = new Dictionary(); - } + /// + /// Timer used to wait until password changes have stopped before updating the dictionary + /// + CTimer PasswordTimer; + /// + /// Timer length + /// + public long PasswordTimerElapsedMs = 5000; - /// - /// Initialize password manager - /// - public void Initialize() - { - if (Passwords == null) - Passwords = new Dictionary(); + /// + /// Boolean event + /// + public event EventHandler BoolChange; + /// + /// Ushort event + /// + public event EventHandler UshrtChange; + /// + /// String event + /// + public event EventHandler StringChange; + /// + /// Event to notify clients of an updated password at the specified index (uint) + /// + public static event EventHandler PasswordChange; - if (_passwords == null) - _passwords = new Dictionary(); - - OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); - } - - /// - /// Updates password stored in the dictonary - /// - /// - /// - /// - /// UpdatePassword method - /// - public void UpdatePassword(ushort key, string password) - { - // validate the parameters - if (key > 0 && string.IsNullOrEmpty(password)) + /// + /// Constructor + /// + public PasswordManager() { - Debug.LogDebug("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password); - return; + } - try + /// + /// Initialize password manager + /// + public void Initialize() { - // if key exists, update the value - if (_passwords.ContainsKey(key)) - _passwords[key] = password; - // else add the key & value - else - _passwords.Add(key, password); + if (Passwords == null) + Passwords = new Dictionary(); - Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]); + if (_passwords == null) + _passwords = new Dictionary(); - if (PasswordTimer == null) + OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); + } + + /// + /// Updates password stored in the dictonary + /// + /// + /// + /// + /// UpdatePassword method + /// + public void UpdatePassword(ushort key, string password) + { + // validate the parameters + if (key > 0 && string.IsNullOrEmpty(password)) { - PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false }; - PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e); - PasswordTimer.Start(); - Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started"); - OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange); + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password)); + return; } - else - { - PasswordTimer.Stop(); - PasswordTimer.Interval = PasswordTimerElapsedMs; - PasswordTimer.Start(); - Debug.LogDebug("PasswordManager.UpdatePassword: Timer Reset"); - } - } - catch (Exception e) - { - var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e); - Debug.LogError(e, msg); - Debug.LogVerbose("Stack Trace:\r{0}", e.StackTrace); - } - } - /// - /// Timer callback function - /// - private void PasswordTimerElapsed(object sender, ElapsedEventArgs e) - { - try - { - PasswordTimer.Stop(); - Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Timer Stopped"); - OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange); - foreach (var pw in _passwords) + try { - // if key exists, continue - if (Passwords.ContainsKey(pw.Key)) - { - Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value); - if (Passwords[pw.Key] != _passwords[pw.Key]) - { - Passwords[pw.Key] = _passwords[pw.Key]; - Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]); - OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange); - } - } + // if key exists, update the value + if(_passwords.ContainsKey(key)) + _passwords[key] = password; // else add the key & value else + _passwords.Add(key, password); + + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key])); + + if (PasswordTimer == null) { - Passwords.Add(pw.Key, pw.Value); + PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs); + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started")); + OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange); + } + else + { + PasswordTimer.Reset(PasswordTimerElapsedMs); + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset")); } } - OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); + catch (Exception e) + { + var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e); + Debug.Console(1, msg); + } } - catch (Exception ex) + + /// + /// CTimer callback function + /// + private void PasswordTimerElapsed() { - var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message); - Debug.LogError(ex, msg); - Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace); + try + { + PasswordTimer.Stop(); + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped")); + OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange); + foreach (var pw in _passwords) + { + // if key exists, continue + if (Passwords.ContainsKey(pw.Key)) + { + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value)); + if (Passwords[pw.Key] != _passwords[pw.Key]) + { + Passwords[pw.Key] = _passwords[pw.Key]; + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key])); + OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange); + } + } + // else add the key & value + else + { + Passwords.Add(pw.Key, pw.Value); + } + } + OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); + } + catch (Exception e) + { + var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e); + Debug.Console(1, msg); + } } - } - /// - /// Method to change the default timer value, (default 5000ms/5s) - /// - /// - /// - /// PasswordTimerMs method - /// - public void PasswordTimerMs(ushort time) - { - PasswordTimerElapsedMs = Convert.ToInt64(time); - } - - /// - /// Helper method for debugging to see what passwords are in the lists - /// - public void ListPasswords() - { - Debug.LogInformation("PasswordManager.ListPasswords:\r"); - foreach (var pw in Passwords) - Debug.LogInformation("Passwords[{0}]: {1}\r", pw.Key, pw.Value); - Debug.LogInformation("\n"); - foreach (var pw in _passwords) - Debug.LogInformation("_passwords[{0}]: {1}\r", pw.Key, pw.Value); - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) + /// + /// Method to change the default timer value, (default 5000ms/5s) + /// + /// + /// + /// PasswordTimerMs method + /// + public void PasswordTimerMs(ushort time) { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); + PasswordTimerElapsedMs = Convert.ToInt64(time); } - } - /// - /// Protected ushort change event handler - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) + /// + /// Helper method for debugging to see what passwords are in the lists + /// + public void ListPasswords() { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); + Debug.Console(0, "PasswordManager.ListPasswords:\r"); + foreach (var pw in Passwords) + Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value); + Debug.Console(0, "\n"); + foreach (var pw in _passwords) + Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value); } - } - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } } - } - /// - /// Protected password change event handler - /// - /// - /// - /// - protected void OnPasswordChange(string value, ushort index, ushort type) - { - var handler = PasswordChange; - if (handler != null) + /// + /// Protected ushort change event handler + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - PasswordChange(this, args); + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + + /// + /// Protected password change event handler + /// + /// + /// + /// + protected void OnPasswordChange(string value, ushort index, ushort type) + { + var handler = PasswordChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + PasswordChange(this, args); + } } } } \ No newline at end of file diff --git a/src/PepperDash.Core/PepperDash.Core.csproj b/src/PepperDash.Core/PepperDash.Core.csproj index 4cd091ef..747aefb6 100644 --- a/src/PepperDash.Core/PepperDash.Core.csproj +++ b/src/PepperDash.Core/PepperDash.Core.csproj @@ -5,7 +5,7 @@ PepperDash.Core PepperDashCore - net8 + net472 true en bin\$(Configuration)\ @@ -35,23 +35,25 @@ - - - - - - global,NewtonsoftJson - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/PepperDash.Core/SystemInfo/EventArgs and Constants.cs b/src/PepperDash.Core/SystemInfo/EventArgs and Constants.cs index e6d697f3..a83f8901 100644 --- a/src/PepperDash.Core/SystemInfo/EventArgs and Constants.cs +++ b/src/PepperDash.Core/SystemInfo/EventArgs and Constants.cs @@ -4,68 +4,68 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.SystemInfo; - +namespace PepperDash.Core.SystemInfo +{ /// /// Constants /// public class SystemInfoConstants { - /// - /// - /// + /// + /// + /// public const ushort BoolValueChange = 1; /// /// /// - public const ushort CompleteBoolChange = 2; + public const ushort CompleteBoolChange = 2; /// /// /// - public const ushort BusyBoolChange = 3; - - /// - /// - /// + public const ushort BusyBoolChange = 3; + + /// + /// + /// public const ushort UshortValueChange = 101; - /// - /// - /// + /// + /// + /// public const ushort StringValueChange = 201; /// /// /// - public const ushort ConsoleResponseChange = 202; + public const ushort ConsoleResponseChange = 202; /// /// /// - public const ushort ProcessorUptimeChange = 203; + public const ushort ProcessorUptimeChange = 203; /// /// /// - public const ushort ProgramUptimeChange = 204; + public const ushort ProgramUptimeChange = 204; - /// - /// - /// + /// + /// + /// public const ushort ObjectChange = 301; /// /// /// - public const ushort ProcessorConfigChange = 302; + public const ushort ProcessorConfigChange = 302; /// /// /// - public const ushort EthernetConfigChange = 303; + public const ushort EthernetConfigChange = 303; /// /// /// - public const ushort ControlSubnetConfigChange = 304; + public const ushort ControlSubnetConfigChange = 304; /// /// /// - public const ushort ProgramConfigChange = 305; + public const ushort ProgramConfigChange = 305; } /// @@ -73,18 +73,18 @@ namespace PepperDash.Core.SystemInfo; /// public class ProcessorChangeEventArgs : EventArgs { - /// - /// - /// + /// + /// + /// public ProcessorInfo Processor { get; set; } /// /// /// - public ushort Type { get; set; } + public ushort Type { get; set; } /// /// /// - public ushort Index { get; set; } + public ushort Index { get; set; } /// /// Constructor @@ -119,18 +119,18 @@ namespace PepperDash.Core.SystemInfo; /// public class EthernetChangeEventArgs : EventArgs { - /// - /// - /// + /// + /// + /// public EthernetInfo Adapter { get; set; } /// /// /// - public ushort Type { get; set; } + public ushort Type { get; set; } /// /// /// - public ushort Index { get; set; } + public ushort Index { get; set; } /// /// Constructor @@ -143,7 +143,7 @@ namespace PepperDash.Core.SystemInfo; /// /// Constructor overload /// - /// + /// /// public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type) { @@ -154,9 +154,9 @@ namespace PepperDash.Core.SystemInfo; /// /// Constructor overload /// - /// + /// /// - /// + /// public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index) { Adapter = ethernet; @@ -170,18 +170,18 @@ namespace PepperDash.Core.SystemInfo; /// public class ControlSubnetChangeEventArgs : EventArgs { - /// - /// - /// + /// + /// + /// public ControlSubnetInfo Adapter { get; set; } /// /// /// - public ushort Type { get; set; } + public ushort Type { get; set; } /// /// /// - public ushort Index { get; set; } + public ushort Index { get; set; } /// /// Constructor @@ -216,18 +216,18 @@ namespace PepperDash.Core.SystemInfo; /// public class ProgramChangeEventArgs : EventArgs { - /// - /// - /// + /// + /// + /// public ProgramInfo Program { get; set; } /// /// /// - public ushort Type { get; set; } + public ushort Type { get; set; } /// /// /// - public ushort Index { get; set; } + public ushort Index { get; set; } /// /// Constructor @@ -240,7 +240,7 @@ namespace PepperDash.Core.SystemInfo; /// /// Constructor overload /// - /// + /// /// public ProgramChangeEventArgs(ProgramInfo program, ushort type) { @@ -251,13 +251,14 @@ namespace PepperDash.Core.SystemInfo; /// /// Constructor overload /// - /// + /// /// - /// + /// public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index) { Program = program; Type = type; Index = index; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/SystemInfo/SystemInfoConfig.cs b/src/PepperDash.Core/SystemInfo/SystemInfoConfig.cs index 950abecb..8dc3acaf 100644 --- a/src/PepperDash.Core/SystemInfo/SystemInfoConfig.cs +++ b/src/PepperDash.Core/SystemInfo/SystemInfoConfig.cs @@ -4,52 +4,52 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.SystemInfo; - +namespace PepperDash.Core.SystemInfo +{ /// /// Processor info class /// public class ProcessorInfo { - /// - /// - /// + /// + /// + /// public string Model { get; set; } - /// - /// - /// + /// + /// + /// public string SerialNumber { get; set; } - /// - /// - /// + /// + /// + /// public string Firmware { get; set; } - /// - /// - /// + /// + /// + /// public string FirmwareDate { get; set; } - /// - /// - /// + /// + /// + /// public string OsVersion { get; set; } - /// - /// - /// + /// + /// + /// public string RuntimeEnvironment { get; set; } - /// - /// - /// + /// + /// + /// public string DevicePlatform { get; set; } - /// - /// - /// + /// + /// + /// public string ModuleDirectory { get; set; } - /// - /// - /// + /// + /// + /// public string LocalTimeZone { get; set; } - /// - /// - /// + /// + /// + /// public string ProgramIdTag { get; set; } /// @@ -66,45 +66,45 @@ namespace PepperDash.Core.SystemInfo; /// public class EthernetInfo { - /// - /// - /// + /// + /// + /// public ushort DhcpIsOn { get; set; } - /// - /// - /// + /// + /// + /// public string Hostname { get; set; } - /// - /// - /// + /// + /// + /// public string MacAddress { get; set; } - /// - /// - /// + /// + /// + /// public string IpAddress { get; set; } - /// - /// - /// + /// + /// + /// public string Subnet { get; set; } - /// - /// - /// + /// + /// + /// public string Gateway { get; set; } - /// - /// - /// + /// + /// + /// public string Dns1 { get; set; } - /// - /// - /// + /// + /// + /// public string Dns2 { get; set; } - /// - /// - /// + /// + /// + /// public string Dns3 { get; set; } - /// - /// - /// + /// + /// + /// public string Domain { get; set; } /// @@ -121,29 +121,29 @@ namespace PepperDash.Core.SystemInfo; /// public class ControlSubnetInfo { - /// - /// - /// + /// + /// + /// public ushort Enabled { get; set; } - /// - /// - /// + /// + /// + /// public ushort IsInAutomaticMode { get; set; } - /// - /// - /// + /// + /// + /// public string MacAddress { get; set; } - /// - /// - /// + /// + /// + /// public string IpAddress { get; set; } - /// - /// - /// + /// + /// + /// public string Subnet { get; set; } - /// - /// - /// + /// + /// + /// public string RouterPrefix { get; set; } /// @@ -160,37 +160,37 @@ namespace PepperDash.Core.SystemInfo; /// public class ProgramInfo { - /// - /// - /// + /// + /// + /// public string Name { get; set; } - /// - /// - /// + /// + /// + /// public string Header { get; set; } - /// - /// - /// + /// + /// + /// public string System { get; set; } - /// - /// - /// + /// + /// + /// public string ProgramIdTag { get; set; } - /// - /// - /// + /// + /// + /// public string CompileTime { get; set; } - /// - /// - /// + /// + /// + /// public string Database { get; set; } - /// - /// - /// + /// + /// + /// public string Environment { get; set; } - /// - /// - /// + /// + /// + /// public string Programmer { get; set; } /// @@ -200,4 +200,5 @@ namespace PepperDash.Core.SystemInfo; { } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/SystemInfo/SystemInfoToSimpl.cs b/src/PepperDash.Core/SystemInfo/SystemInfoToSimpl.cs index 00309676..45f787f5 100644 --- a/src/PepperDash.Core/SystemInfo/SystemInfoToSimpl.cs +++ b/src/PepperDash.Core/SystemInfo/SystemInfoToSimpl.cs @@ -4,37 +4,37 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Core.SystemInfo; - +namespace PepperDash.Core.SystemInfo +{ /// /// System Info class /// public class SystemInfoToSimpl { - /// - /// Notifies of bool change - /// + /// + /// Notifies of bool change + /// public event EventHandler BoolChange; - /// - /// Notifies of string change - /// + /// + /// Notifies of string change + /// public event EventHandler StringChange; - /// - /// Notifies of processor change - /// + /// + /// Notifies of processor change + /// public event EventHandler ProcessorChange; - /// - /// Notifies of ethernet change - /// + /// + /// Notifies of ethernet change + /// public event EventHandler EthernetChange; - /// - /// Notifies of control subnet change - /// + /// + /// Notifies of control subnet change + /// public event EventHandler ControlSubnetChange; - /// - /// Notifies of program change - /// + /// + /// Notifies of program change + /// public event EventHandler ProgramChange; /// @@ -336,10 +336,10 @@ namespace PepperDash.Core.SystemInfo; /// /// private method to parse console messages /// - /// + /// /// - /// - /// + /// + /// /// private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd) { @@ -467,4 +467,5 @@ namespace PepperDash.Core.SystemInfo; ProgramChange(this, args); } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Web/BouncyCertificate.cs b/src/PepperDash.Core/Web/BouncyCertificate.cs index 148b6eb1..9e885094 100644 --- a/src/PepperDash.Core/Web/BouncyCertificate.cs +++ b/src/PepperDash.Core/Web/BouncyCertificate.cs @@ -20,336 +20,352 @@ using Org.BouncyCastle.Crypto.Operators; using BigInteger = Org.BouncyCastle.Math.BigInteger; using X509Certificate = Org.BouncyCastle.X509.X509Certificate; -namespace PepperDash.Core; - -/// -/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/ -/// -internal class BouncyCertificate +namespace PepperDash.Core { - public string CertificatePassword { get; set; } = "password"; - public X509Certificate2 LoadCertificate(string issuerFileName, string password) - { - // We need to pass 'Exportable', otherwise we can't get the private key. - var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable); - return issuerCertificate; - } - - public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages) - { - // It's self-signed, so these are the same. - var issuerName = issuerCertificate.Subject; - - var random = GetSecureRandom(); - var subjectKeyPair = GenerateKeyPair(random, 2048); - - var issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey); - - var serialNumber = GenerateSerialNumber(random); - var issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber()); - - const bool isCertificateAuthority = false; - var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, - subjectAlternativeNames, issuerName, issuerKeyPair, - issuerSerialNumber, isCertificateAuthority, - usages); - return ConvertCertificate(certificate, subjectKeyPair, random); - } - - public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages) - { - // It's self-signed, so these are the same. - var issuerName = subjectName; - - var random = GetSecureRandom(); - var subjectKeyPair = GenerateKeyPair(random, 2048); - - // It's self-signed, so these are the same. - var issuerKeyPair = subjectKeyPair; - - var serialNumber = GenerateSerialNumber(random); - var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number. - - const bool isCertificateAuthority = true; - var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, - subjectAlternativeNames, issuerName, issuerKeyPair, - issuerSerialNumber, isCertificateAuthority, - usages); - return ConvertCertificate(certificate, subjectKeyPair, random); - } - - public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages) - { - // It's self-signed, so these are the same. - var issuerName = subjectName; - - var random = GetSecureRandom(); - var subjectKeyPair = GenerateKeyPair(random, 2048); - - // It's self-signed, so these are the same. - var issuerKeyPair = subjectKeyPair; - - var serialNumber = GenerateSerialNumber(random); - var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number. - - const bool isCertificateAuthority = false; - var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, - subjectAlternativeNames, issuerName, issuerKeyPair, - issuerSerialNumber, isCertificateAuthority, - usages); - return ConvertCertificate(certificate, subjectKeyPair, random); - } - - private SecureRandom GetSecureRandom() - { - // Since we're on Windows, we'll use the CryptoAPI one (on the assumption - // that it might have access to better sources of entropy than the built-in - // Bouncy Castle ones): - var randomGenerator = new CryptoApiRandomGenerator(); - var random = new SecureRandom(randomGenerator); - return random; - } - - private X509Certificate GenerateCertificate(SecureRandom random, - string subjectName, - AsymmetricCipherKeyPair subjectKeyPair, - BigInteger subjectSerialNumber, - string[] subjectAlternativeNames, - string issuerName, - AsymmetricCipherKeyPair issuerKeyPair, - BigInteger issuerSerialNumber, - bool isCertificateAuthority, - KeyPurposeID[] usages) - { - var certificateGenerator = new X509V3CertificateGenerator(); - - certificateGenerator.SetSerialNumber(subjectSerialNumber); - - var issuerDN = new X509Name(issuerName); - certificateGenerator.SetIssuerDN(issuerDN); - - // Note: The subject can be omitted if you specify a subject alternative name (SAN). - var subjectDN = new X509Name(subjectName); - certificateGenerator.SetSubjectDN(subjectDN); - - // Our certificate needs valid from/to values. - var notBefore = DateTime.UtcNow.Date; - var notAfter = notBefore.AddYears(2); - - certificateGenerator.SetNotBefore(notBefore); - certificateGenerator.SetNotAfter(notAfter); - - // The subject's public key goes in the certificate. - certificateGenerator.SetPublicKey(subjectKeyPair.Public); - - AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber); - AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair); - //AddBasicConstraints(certificateGenerator, isCertificateAuthority); - - if (usages != null && usages.Any()) - AddExtendedKeyUsage(certificateGenerator, usages); - - if (subjectAlternativeNames != null && subjectAlternativeNames.Any()) - AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames); - - // Set the signature algorithm. This is used to generate the thumbprint which is then signed - // with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong. - const string signatureAlgorithm = "SHA256WithRSA"; - - // The certificate is signed with the issuer's private key. - ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random); - var certificate = certificateGenerator.Generate(signatureFactory); - return certificate; - } - /// - /// The certificate needs a serial number. This is used for revocation, - /// and usually should be an incrementing index (which makes it easier to revoke a range of certificates). - /// Since we don't have anywhere to store the incrementing index, we can just use a random number. + /// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/ /// - /// - /// - private BigInteger GenerateSerialNumber(SecureRandom random) + internal class BouncyCertificate { - var serialNumber = - BigIntegers.CreateRandomInRange( - BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); - return serialNumber; - } - - /// - /// Generate a key pair. - /// - /// The random number generator. - /// The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days. - /// - private AsymmetricCipherKeyPair GenerateKeyPair(SecureRandom random, int strength) - { - var keyGenerationParameters = new KeyGenerationParameters(random, strength); - - var keyPairGenerator = new RsaKeyPairGenerator(); - keyPairGenerator.Init(keyGenerationParameters); - var subjectKeyPair = keyPairGenerator.GenerateKeyPair(); - return subjectKeyPair; - } - - /// - /// Add the Authority Key Identifier. According to http://www.alvestrand.no/objectid/2.5.29.35.html, this - /// identifies the public key to be used to verify the signature on this certificate. - /// In a certificate chain, this corresponds to the "Subject Key Identifier" on the *issuer* certificate. - /// The Bouncy Castle documentation, at http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation, - /// shows how to create this from the issuing certificate. Since we're creating a self-signed certificate, we have to do this slightly differently. - /// - /// - /// - /// - /// - private void AddAuthorityKeyIdentifier(X509V3CertificateGenerator certificateGenerator, - X509Name issuerDN, - AsymmetricCipherKeyPair issuerKeyPair, - BigInteger issuerSerialNumber) - { - var authorityKeyIdentifierExtension = - new AuthorityKeyIdentifier( - SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKeyPair.Public), - new GeneralNames(new GeneralName(issuerDN)), - issuerSerialNumber); - certificateGenerator.AddExtension( - X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifierExtension); - } - - /// - /// Add the "Subject Alternative Names" extension. Note that you have to repeat - /// the value from the "Subject Name" property. - /// - /// - /// - private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator, - IEnumerable subjectAlternativeNames) - { - var subjectAlternativeNamesExtension = - new DerSequence( - subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name)) - .ToArray()); - certificateGenerator.AddExtension( - X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension); - } - - /// - /// Add the "Extended Key Usage" extension, specifying (for example) "server authentication". - /// - /// - /// - private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages) - { - certificateGenerator.AddExtension( - X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages)); - } - - /// - /// Add the "Basic Constraints" extension. - /// - /// - /// - private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator, - bool isCertificateAuthority) - { - certificateGenerator.AddExtension( - X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority)); - } - - /// - /// Add the Subject Key Identifier. - /// - /// - /// - private void AddSubjectKeyIdentifier(X509V3CertificateGenerator certificateGenerator, - AsymmetricCipherKeyPair subjectKeyPair) - { - var subjectKeyIdentifierExtension = - new SubjectKeyIdentifier( - SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public)); - certificateGenerator.AddExtension( - X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifierExtension); - } - - private X509Certificate2 ConvertCertificate(X509Certificate certificate, - AsymmetricCipherKeyPair subjectKeyPair, - SecureRandom random) - { - // Now to convert the Bouncy Castle certificate to a .NET certificate. - // See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx - // ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that. - var store = new Pkcs12StoreBuilder().Build(); - - // What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name". - string friendlyName = certificate.SubjectDN.ToString(); - - // Add the certificate. - var certificateEntry = new X509CertificateEntry(certificate); - store.SetCertificateEntry(friendlyName, certificateEntry); - - // Add the private key. - store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry }); - - // Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream. - // It needs a password. Since we'll remove this later, it doesn't particularly matter what we use. - - var stream = new MemoryStream(); - store.Save(stream, CertificatePassword.ToCharArray(), random); - - var convertedCertificate = - new X509Certificate2(stream.ToArray(), - CertificatePassword, - X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); - return convertedCertificate; - } - - public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName) - { - // This password is the one attached to the PFX file. Use 'null' for no password. - // Create PFX (PKCS #12) with private key - try + public string CertificatePassword { get; set; } = "password"; + public X509Certificate2 LoadCertificate(string issuerFileName, string password) { - var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword); - File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx); + // We need to pass 'Exportable', otherwise we can't get the private key. + var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable); + return issuerCertificate; } - catch (Exception ex) + + /// + /// IssueCertificate method + /// + public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages) { - CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message)); + // It's self-signed, so these are the same. + var issuerName = issuerCertificate.Subject; + + var random = GetSecureRandom(); + var subjectKeyPair = GenerateKeyPair(random, 2048); + + var issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey); + + var serialNumber = GenerateSerialNumber(random); + var issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber()); + + const bool isCertificateAuthority = false; + var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, + subjectAlternativeNames, issuerName, issuerKeyPair, + issuerSerialNumber, isCertificateAuthority, + usages); + return ConvertCertificate(certificate, subjectKeyPair, random); } - // Create Base 64 encoded CER (public key only) - using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false)) + + /// + /// CreateCertificateAuthorityCertificate method + /// + public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages) { + // It's self-signed, so these are the same. + var issuerName = subjectName; + + var random = GetSecureRandom(); + var subjectKeyPair = GenerateKeyPair(random, 2048); + + // It's self-signed, so these are the same. + var issuerKeyPair = subjectKeyPair; + + var serialNumber = GenerateSerialNumber(random); + var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number. + + const bool isCertificateAuthority = true; + var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, + subjectAlternativeNames, issuerName, issuerKeyPair, + issuerSerialNumber, isCertificateAuthority, + usages); + return ConvertCertificate(certificate, subjectKeyPair, random); + } + + /// + /// CreateSelfSignedCertificate method + /// + public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages) + { + // It's self-signed, so these are the same. + var issuerName = subjectName; + + var random = GetSecureRandom(); + var subjectKeyPair = GenerateKeyPair(random, 2048); + + // It's self-signed, so these are the same. + var issuerKeyPair = subjectKeyPair; + + var serialNumber = GenerateSerialNumber(random); + var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number. + + const bool isCertificateAuthority = false; + var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber, + subjectAlternativeNames, issuerName, issuerKeyPair, + issuerSerialNumber, isCertificateAuthority, + usages); + return ConvertCertificate(certificate, subjectKeyPair, random); + } + + private SecureRandom GetSecureRandom() + { + // Since we're on Windows, we'll use the CryptoAPI one (on the assumption + // that it might have access to better sources of entropy than the built-in + // Bouncy Castle ones): + var randomGenerator = new CryptoApiRandomGenerator(); + var random = new SecureRandom(randomGenerator); + return random; + } + + private X509Certificate GenerateCertificate(SecureRandom random, + string subjectName, + AsymmetricCipherKeyPair subjectKeyPair, + BigInteger subjectSerialNumber, + string[] subjectAlternativeNames, + string issuerName, + AsymmetricCipherKeyPair issuerKeyPair, + BigInteger issuerSerialNumber, + bool isCertificateAuthority, + KeyPurposeID[] usages) + { + var certificateGenerator = new X509V3CertificateGenerator(); + + certificateGenerator.SetSerialNumber(subjectSerialNumber); + + var issuerDN = new X509Name(issuerName); + certificateGenerator.SetIssuerDN(issuerDN); + + // Note: The subject can be omitted if you specify a subject alternative name (SAN). + var subjectDN = new X509Name(subjectName); + certificateGenerator.SetSubjectDN(subjectDN); + + // Our certificate needs valid from/to values. + var notBefore = DateTime.UtcNow.Date; + var notAfter = notBefore.AddYears(2); + + certificateGenerator.SetNotBefore(notBefore); + certificateGenerator.SetNotAfter(notAfter); + + // The subject's public key goes in the certificate. + certificateGenerator.SetPublicKey(subjectKeyPair.Public); + + AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber); + AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair); + //AddBasicConstraints(certificateGenerator, isCertificateAuthority); + + if (usages != null && usages.Any()) + AddExtendedKeyUsage(certificateGenerator, usages); + + if (subjectAlternativeNames != null && subjectAlternativeNames.Any()) + AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames); + + // Set the signature algorithm. This is used to generate the thumbprint which is then signed + // with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong. + const string signatureAlgorithm = "SHA256WithRSA"; + + // The certificate is signed with the issuer's private key. + ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random); + var certificate = certificateGenerator.Generate(signatureFactory); + return certificate; + } + + /// + /// The certificate needs a serial number. This is used for revocation, + /// and usually should be an incrementing index (which makes it easier to revoke a range of certificates). + /// Since we don't have anywhere to store the incrementing index, we can just use a random number. + /// + /// + /// + private BigInteger GenerateSerialNumber(SecureRandom random) + { + var serialNumber = + BigIntegers.CreateRandomInRange( + BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); + return serialNumber; + } + + /// + /// Generate a key pair. + /// + /// The random number generator. + /// The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days. + /// + private AsymmetricCipherKeyPair GenerateKeyPair(SecureRandom random, int strength) + { + var keyGenerationParameters = new KeyGenerationParameters(random, strength); + + var keyPairGenerator = new RsaKeyPairGenerator(); + keyPairGenerator.Init(keyGenerationParameters); + var subjectKeyPair = keyPairGenerator.GenerateKeyPair(); + return subjectKeyPair; + } + + /// + /// Add the Authority Key Identifier. According to http://www.alvestrand.no/objectid/2.5.29.35.html, this + /// identifies the public key to be used to verify the signature on this certificate. + /// In a certificate chain, this corresponds to the "Subject Key Identifier" on the *issuer* certificate. + /// The Bouncy Castle documentation, at http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation, + /// shows how to create this from the issuing certificate. Since we're creating a self-signed certificate, we have to do this slightly differently. + /// + /// + /// + /// + /// + private void AddAuthorityKeyIdentifier(X509V3CertificateGenerator certificateGenerator, + X509Name issuerDN, + AsymmetricCipherKeyPair issuerKeyPair, + BigInteger issuerSerialNumber) + { + var authorityKeyIdentifierExtension = + new AuthorityKeyIdentifier( + SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKeyPair.Public), + new GeneralNames(new GeneralName(issuerDN)), + issuerSerialNumber); + certificateGenerator.AddExtension( + X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifierExtension); + } + + /// + /// Add the "Subject Alternative Names" extension. Note that you have to repeat + /// the value from the "Subject Name" property. + /// + /// + /// + private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator, + IEnumerable subjectAlternativeNames) + { + var subjectAlternativeNamesExtension = + new DerSequence( + subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name)) + .ToArray()); + certificateGenerator.AddExtension( + X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension); + } + + /// + /// Add the "Extended Key Usage" extension, specifying (for example) "server authentication". + /// + /// + /// + private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages) + { + certificateGenerator.AddExtension( + X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages)); + } + + /// + /// Add the "Basic Constraints" extension. + /// + /// + /// + private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator, + bool isCertificateAuthority) + { + certificateGenerator.AddExtension( + X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority)); + } + + /// + /// Add the Subject Key Identifier. + /// + /// + /// + private void AddSubjectKeyIdentifier(X509V3CertificateGenerator certificateGenerator, + AsymmetricCipherKeyPair subjectKeyPair) + { + var subjectKeyIdentifierExtension = + new SubjectKeyIdentifier( + SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public)); + certificateGenerator.AddExtension( + X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifierExtension); + } + + private X509Certificate2 ConvertCertificate(X509Certificate certificate, + AsymmetricCipherKeyPair subjectKeyPair, + SecureRandom random) + { + // Now to convert the Bouncy Castle certificate to a .NET certificate. + // See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx + // ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that. + var store = new Pkcs12StoreBuilder().Build(); + + // What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name". + string friendlyName = certificate.SubjectDN.ToString(); + + // Add the certificate. + var certificateEntry = new X509CertificateEntry(certificate); + store.SetCertificateEntry(friendlyName, certificateEntry); + + // Add the private key. + store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry }); + + // Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream. + // It needs a password. Since we'll remove this later, it doesn't particularly matter what we use. + + var stream = new MemoryStream(); + store.Save(stream, CertificatePassword.ToCharArray(), random); + + var convertedCertificate = + new X509Certificate2(stream.ToArray(), + CertificatePassword, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + return convertedCertificate; + } + + /// + /// WriteCertificate method + /// + public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName) + { + // This password is the one attached to the PFX file. Use 'null' for no password. + // Create PFX (PKCS #12) with private key try { - var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)); - writer.Write(contents); + var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword); + File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx); } catch (Exception ex) { - CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\r\n{0}", ex.Message)); + CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message)); + } + // Create Base 64 encoded CER (public key only) + using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false)) + { + try + { + var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)); + writer.Write(contents); + } + catch (Exception ex) + { + CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\r\n{0}", ex.Message)); + } } } - } - public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl) - { - bool bRet = false; - - try + /// + /// AddCertToStore method + /// + public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl) { - var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl); - store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite); - store.Add(cert); + bool bRet = false; - store.Close(); - bRet = true; - } - catch (Exception ex) - { - CrestronConsole.PrintLine(string.Format("AddCertToStore Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace)); - } + try + { + var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl); + store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite); + store.Add(cert); - return bRet; + store.Close(); + bRet = true; + } + catch (Exception ex) + { + CrestronConsole.PrintLine(string.Format("AddCertToStore Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace)); + } + + return bRet; + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/Web/RequestHandlers/DefaultRequestHandler.cs b/src/PepperDash.Core/Web/RequestHandlers/DefaultRequestHandler.cs index 2fee0a62..154dc01e 100644 --- a/src/PepperDash.Core/Web/RequestHandlers/DefaultRequestHandler.cs +++ b/src/PepperDash.Core/Web/RequestHandlers/DefaultRequestHandler.cs @@ -1,10 +1,10 @@ using Crestron.SimplSharp.WebScripting; -namespace PepperDash.Core.Web.RequestHandlers; - - /// - /// Web API default request handler - /// +namespace PepperDash.Core.Web.RequestHandlers +{ + /// + /// Represents a DefaultRequestHandler + /// public class DefaultRequestHandler : WebApiBaseRequestHandler { /// @@ -13,4 +13,5 @@ namespace PepperDash.Core.Web.RequestHandlers; public DefaultRequestHandler() : base(true) { } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs b/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs index e8b3e85b..927092db 100644 --- a/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs +++ b/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs @@ -3,160 +3,164 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace PepperDash.Core.Web.RequestHandlers; - -public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler +namespace PepperDash.Core.Web.RequestHandlers { - private readonly Dictionary> _handlers; - protected readonly bool EnableCors; - - /// - /// Constructor - /// - protected WebApiBaseRequestAsyncHandler(bool enableCors) + public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler { - EnableCors = enableCors; + private readonly Dictionary> _handlers; + protected readonly bool EnableCors; - _handlers = new Dictionary> + /// + /// Constructor + /// + protected WebApiBaseRequestAsyncHandler(bool enableCors) { - {"CONNECT", HandleConnect}, - {"DELETE", HandleDelete}, - {"GET", HandleGet}, - {"HEAD", HandleHead}, - {"OPTIONS", HandleOptions}, - {"PATCH", HandlePatch}, - {"POST", HandlePost}, - {"PUT", HandlePut}, - {"TRACE", HandleTrace} - }; - } + EnableCors = enableCors; - /// - /// Constructor - /// - protected WebApiBaseRequestAsyncHandler() - : this(false) - { - } - - /// - /// Handles CONNECT method requests - /// - /// - protected virtual async Task HandleConnect(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles DELETE method requests - /// - /// - protected virtual async Task HandleDelete(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles GET method requests - /// - /// - protected virtual async Task HandleGet(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles HEAD method requests - /// - /// - protected virtual async Task HandleHead(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles OPTIONS method requests - /// - /// - protected virtual async Task HandleOptions(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles PATCH method requests - /// - /// - protected virtual async Task HandlePatch(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles POST method requests - /// - /// - protected virtual async Task HandlePost(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles PUT method requests - /// - /// - protected virtual async Task HandlePut(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles TRACE method requests - /// - /// - protected virtual async Task HandleTrace(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Process request - /// - /// - public void ProcessRequest(HttpCwsContext context) - { - if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func handler)) - { - return; + _handlers = new Dictionary> + { + {"CONNECT", HandleConnect}, + {"DELETE", HandleDelete}, + {"GET", HandleGet}, + {"HEAD", HandleHead}, + {"OPTIONS", HandleOptions}, + {"PATCH", HandlePatch}, + {"POST", HandlePost}, + {"PUT", HandlePut}, + {"TRACE", HandleTrace} + }; } - if (EnableCors) + /// + /// Constructor + /// + protected WebApiBaseRequestAsyncHandler() + : this(false) { - context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); - context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); } - var handlerTask = handler(context); + /// + /// Handles CONNECT method requests + /// + /// + protected virtual async Task HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } - handlerTask.GetAwaiter().GetResult(); + /// + /// Handles DELETE method requests + /// + /// + protected virtual async Task HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected virtual async Task HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected virtual async Task HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected virtual async Task HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected virtual async Task HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected virtual async Task HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected virtual async Task HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected virtual async Task HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Process request + /// + /// + /// + /// ProcessRequest method + /// + public void ProcessRequest(HttpCwsContext context) + { + if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func handler)) + { + return; + } + + if (EnableCors) + { + context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); + context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); + } + + var handlerTask = handler(context); + + handlerTask.GetAwaiter().GetResult(); + } } } diff --git a/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs b/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs index 0d36a863..a02f3497 100644 --- a/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs +++ b/src/PepperDash.Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using Crestron.SimplSharp.WebScripting; -namespace PepperDash.Core.Web.RequestHandlers; - +namespace PepperDash.Core.Web.RequestHandlers +{ /// /// CWS Base Handler, implements IHttpCwsHandler /// @@ -164,4 +164,5 @@ namespace PepperDash.Core.Web.RequestHandlers; handler(context); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/Web/WebApiServer.cs b/src/PepperDash.Core/Web/WebApiServer.cs index 814a6f1d..f07aab42 100644 --- a/src/PepperDash.Core/Web/WebApiServer.cs +++ b/src/PepperDash.Core/Web/WebApiServer.cs @@ -1,269 +1,291 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; -using PepperDash.Core.Web.RequestHandlers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using PepperDash.Core.Logging; +using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Core.Web; - -/// -/// Web API server -/// -public class WebApiServer : IKeyName +namespace PepperDash.Core.Web { - private const string SplusKey = "Uninitialized Web API Server"; - private const string DefaultName = "Web API Server"; - private const string DefaultBasePath = "/api"; - - private readonly object _serverLock = new(); - private HttpCwsServer _server; - /// - /// Gets or sets the Key + /// Web API server /// - public string Key { get; private set; } - - /// - /// Gets or sets the Name - /// - public string Name { get; private set; } - - /// - /// Gets or sets the BasePath - /// - public string BasePath { get; private set; } - - /// - /// Gets or sets the IsRegistered - /// - public bool IsRegistered { get; private set; } - - /// - /// Constructor for S+. Make sure to set necessary properties using init method - /// - public WebApiServer() - : this(SplusKey, DefaultName, null) + public class WebApiServer : IKeyName { - } + private const string SplusKey = "Uninitialized Web API Server"; + private const string DefaultName = "Web API Server"; + private const string DefaultBasePath = "/api"; - /// - /// Constructor - /// - /// - /// - public WebApiServer(string key, string basePath) - : this(key, DefaultName, basePath) - { - } + private const uint DebugTrace = 0; + private const uint DebugInfo = 1; + private const uint DebugVerbose = 2; - /// - /// Constructor - /// - /// - /// - /// - public WebApiServer(string key, string name, string basePath) - { - Key = key; - Name = string.IsNullOrEmpty(name) ? DefaultName : name; - BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; + private readonly CCriticalSection _serverLock = new CCriticalSection(); + private HttpCwsServer _server; - this.LogInformation("Creating Web API Server with Key: {Key}, Name: {Name}, BasePath: {BasePath}", Key, Name, BasePath); + /// + /// Gets or sets the Key + /// + public string Key { get; private set; } - if (_server == null) _server = new HttpCwsServer(BasePath); + /// + /// Gets or sets the Name + /// + public string Name { get; private set; } - _server.AuthenticateAllRoutes = false; + /// + /// Gets or sets the BasePath + /// + public string BasePath { get; private set; } - _server.setProcessName(Key); - _server.HttpRequestHandler = new DefaultRequestHandler(); - _server.ReceivedRequestEvent += ReceivedRequestEventHandler; + /// + /// Gets or sets the IsRegistered + /// + public bool IsRegistered { get; private set; } - CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; - CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; - } + //public IHttpCwsHandler HttpRequestHandler + //{ + // get { return _server.HttpRequestHandler; } + // set + // { + // if (_server == null) return; + // _server.HttpRequestHandler = value; + // } + //} - /// - /// Program status event handler - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType != eProgramStatusEventType.Stopping) return; + //public event EventHandler ReceivedRequestEvent + //{ + // add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); } + // remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); } + //} - this.LogInformation("Program stopping. stopping server"); - - Stop(); - } - - /// - /// Ethernet event handler - /// - /// - void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) - { - if (ethernetEventArgs.EthernetEventType != eEthernetEventType.LinkUp) + /// + /// Constructor for S+. Make sure to set necessary properties using init method + /// + public WebApiServer() + : this(SplusKey, DefaultName, null) { - return; } - if (IsRegistered) + /// + /// Constructor + /// + /// + /// + public WebApiServer(string key, string basePath) + : this(key, DefaultName, basePath) { - this.LogInformation("Ethernet link up. Server is already registered."); - return; } - this.LogInformation("Ethernet link up. Starting server"); - - Start(); - } - - // /// - // /// Initialize method - // /// - // public void Initialize(string key, string basePath) - // { - // Key = key; - // BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; - // } - - /// - /// Adds a route to CWS - /// - public void AddRoute(HttpCwsRoute route) - { - if (route == null) + /// + /// Constructor + /// + /// + /// + /// + public WebApiServer(string key, string name, string basePath) { - this.LogWarning("Failed to add route, route parameter is null"); - return; + Key = key; + Name = string.IsNullOrEmpty(name) ? DefaultName : name; + BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; + + if (_server == null) _server = new HttpCwsServer(BasePath); + + _server.AuthenticateAllRoutes = false; + + _server.setProcessName(Key); + _server.HttpRequestHandler = new DefaultRequestHandler(); + + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; } - _server.Routes.Add(route); - - } - - /// - /// Removes a route from CWS - /// - /// - /// - /// RemoveRoute method - /// - public void RemoveRoute(HttpCwsRoute route) - { - if (route == null) + /// + /// Program status event handler + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { - this.LogWarning("Failed to remove route, route parameter is null"); - return; + if (programEventType != eProgramStatusEventType.Stopping) return; + + this.LogInformation("Program stopping. stopping server"); + + Stop(); } - _server.Routes.Remove(route); - } - - /// - /// Sets the fallback request handler that is invoked when no registered route - /// matches an incoming request. Must be called before . - /// - /// The handler to use as the server-level fallback. - public void SetFallbackHandler(IHttpCwsHandler handler) - { - if (handler == null) + /// + /// Ethernet event handler + /// + /// + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) { - this.LogWarning("SetFallbackHandler: handler parameter is null, ignoring"); - return; + // Re-enable the server if the link comes back up and the status should be connected + if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered) + { + this.LogInformation("Ethernet link up. Server is alreedy registered."); + return; + } + + this.LogInformation("Ethernet link up. Starting server"); + + Start(); } - _server.HttpRequestHandler = handler; - } + /// + /// Initialize method + /// + public void Initialize(string key, string basePath) + { + Key = key; + BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; + } - /// - /// GetRouteCollection method - /// - public HttpCwsRouteCollection GetRouteCollection() - { - return _server.Routes; - } + /// + /// Adds a route to CWS + /// + public void AddRoute(HttpCwsRoute route) + { + if (route == null) + { + this.LogWarning("Failed to add route, route parameter is null"); + return; + } - /// - /// Starts CWS instance - /// - public void Start() - { - lock (_serverLock) + _server.Routes.Add(route); + + } + + /// + /// Removes a route from CWS + /// + /// + public void RemoveRoute(HttpCwsRoute route) + { + if (route == null) + { + this.LogWarning("Failed to remove route, route parameter is null"); + return; + } + + _server.Routes.Remove(route); + } + + /// + /// Sets the fallback request handler that is invoked when no registered route + /// matches an incoming request. Must be called before . + /// + /// The handler to use as the server-level fallback. + public void SetFallbackHandler(IHttpCwsHandler handler) + { + if (handler == null) + { + this.LogWarning("SetFallbackHandler: handler parameter is null, ignoring"); + return; + } + + _server.HttpRequestHandler = handler; + } + + /// + /// GetRouteCollection method + /// + public HttpCwsRouteCollection GetRouteCollection() + { + return _server.Routes; + } + + /// + /// Starts CWS instance + /// + public void Start() { try { + _serverLock.Enter(); + if (_server == null) { - this.LogDebug("Server is null, unable to start"); + this.LogWarning("Server is null, unable to start"); return; } if (IsRegistered) { - this.LogDebug("Server has already been started"); + this.LogWarning("Server has already been started"); return; } IsRegistered = _server.Register(); - this.LogDebug("Starting server, registration {0}", IsRegistered ? "was successful" : "failed"); + this.LogInformation("Starting server, registration {registrationResult}", IsRegistered ? "was successful" : "failed"); } catch (Exception ex) { - this.LogException(ex, "Start Exception Message: {0}", ex.Message); - this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace); + this.LogError("Start Exception Message: {message}", ex.Message); + this.LogDebug(ex, "Start Exception StackTrace"); } - } // end lock - } + finally + { + _serverLock.Leave(); + } + } - /// - /// Stop method - /// - public void Stop() - { - lock (_serverLock) + /// + /// Stop method + /// + public void Stop() { try { + _serverLock.Enter(); + if (_server == null) { - this.LogDebug("Server is null or has already been stopped"); + this.LogWarning("Server is null or has already been stopped"); return; } - var unregistered = _server.Unregister(); - IsRegistered = !unregistered; + IsRegistered = _server.Unregister() == false; - this.LogDebug("Stopping server, unregistration {0}", unregistered ? "was successful" : "failed"); + this.LogInformation("Stopping server, unregistration {unregistrationResult}", IsRegistered ? "failed" : "was successful"); + + _server.Dispose(); + _server = null; } catch (Exception ex) { - this.LogException(ex, "Server Stop Exception Message: {0}", ex.Message); + this.LogError("Server Stop Exception Message: {message}", ex.Message); + this.LogDebug(ex, "Server Stop Exception StackTrace"); + } + finally + { + _serverLock.Leave(); } - } // end lock - } - - /// - /// Received request handler - /// - /// - /// This is here for development and testing - /// - /// - /// - public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args) - { - try - { - var req = args.Context?.Request; - this.LogVerbose("ReceivedRequestEventHandler: {Method} {Path}", req?.HttpMethod, req?.Path); } - catch (Exception ex) + + /// + /// Received request handler + /// + /// + /// This is here for development and testing + /// + /// + /// + public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args) { - this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message); + try + { + var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented); + this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j); + } + catch (Exception ex) + { + this.LogError("ReceivedRequestEventHandler Exception Message: {message}", ex.Message); + this.LogDebug(ex, "ReceivedRequestEventHandler Exception StackTrace: {stackTrace}", ex.StackTrace); + } } } } \ No newline at end of file diff --git a/src/PepperDash.Core/WebApi/Presets/Preset.cs b/src/PepperDash.Core/WebApi/Presets/Preset.cs new file mode 100644 index 00000000..88df890c --- /dev/null +++ b/src/PepperDash.Core/WebApi/Presets/Preset.cs @@ -0,0 +1,87 @@ +using System; + +namespace PepperDash.Core.WebApi.Presets +{ + /// + /// Represents a Preset + /// + public class Preset + { + /// + /// ID of preset + /// + public int Id { get; set; } + + /// + /// Gets or sets the UserId + /// + public int UserId { get; set; } + + /// + /// Gets or sets the RoomTypeId + /// + public int RoomTypeId { get; set; } + + /// + /// Gets or sets the PresetName + /// + public string PresetName { get; set; } + + /// + /// Gets or sets the PresetNumber + /// + public int PresetNumber { get; set; } + + /// + /// Gets or sets the Data + /// + public string Data { get; set; } + + /// + /// Constructor + /// + public Preset() + { + PresetName = ""; + PresetNumber = 1; + Data = "{}"; + } + } + + /// + /// Represents a PresetReceivedEventArgs + /// + public class PresetReceivedEventArgs : EventArgs + { + /// + /// True when the preset is found + /// + public bool LookupSuccess { get; private set; } + + /// + /// Gets or sets the ULookupSuccess + /// + public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } } + + /// + /// Gets or sets the Preset + /// + public Preset Preset { get; private set; } + + /// + /// For Simpl+ + /// + public PresetReceivedEventArgs() { } + + /// + /// Constructor + /// + /// + /// + public PresetReceivedEventArgs(Preset preset, bool success) + { + LookupSuccess = success; + Preset = preset; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/WebApi/Presets/User.cs b/src/PepperDash.Core/WebApi/Presets/User.cs new file mode 100644 index 00000000..4044ae7b --- /dev/null +++ b/src/PepperDash.Core/WebApi/Presets/User.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.WebApi.Presets +{ + /// + /// + /// + public class User + { + /// + /// + /// + public int Id { get; set; } + + /// + /// Gets or sets the ExternalId + /// + public string ExternalId { get; set; } + + /// + /// Gets or sets the FirstName + /// + public string FirstName { get; set; } + + /// + /// Gets or sets the LastName + /// + public string LastName { get; set; } + } + + + /// + /// + /// + public class UserReceivedEventArgs : EventArgs + { + /// + /// True when user is found + /// + public bool LookupSuccess { get; private set; } + + /// + /// Gets or sets the ULookupSuccess + /// + public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } } + + /// + /// Gets or sets the User + /// + public User User { get; private set; } + + /// + /// For Simpl+ + /// + public UserReceivedEventArgs() { } + + /// + /// Constructor + /// + /// + /// + public UserReceivedEventArgs(User user, bool success) + { + LookupSuccess = success; + User = user; + } + } + + /// + /// Represents a UserAndRoomMessage + /// + public class UserAndRoomMessage + { + /// + /// + /// + public int UserId { get; set; } + + /// + /// Gets or sets the RoomTypeId + /// + public int RoomTypeId { get; set; } + + /// + /// Gets or sets the PresetNumber + /// + public int PresetNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs b/src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs new file mode 100644 index 00000000..131430a2 --- /dev/null +++ b/src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs @@ -0,0 +1,283 @@ +using System; +using Crestron.SimplSharp; // For Basic SIMPL# Classes +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.Net.Http; +using Crestron.SimplSharp.Net.Https; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core.JsonToSimpl; + + +namespace PepperDash.Core.WebApi.Presets +{ + /// + /// Passcode client for the WebApi + /// + [Obsolete("WebApiPasscodeClient is no longer supported and will be removed in a future release.")] + public class WebApiPasscodeClient : IKeyed + { + /// + /// Notifies when user received + /// + public event EventHandler UserReceived; + + /// + /// Notifies when Preset received + /// + public event EventHandler PresetReceived; + + /// + /// Gets or sets the Key + /// + public string Key { get; private set; } + + //string JsonMasterKey; + + /// + /// An embedded JsonToSimpl master object. + /// + JsonToSimplGenericMaster J2SMaster; + + string UrlBase; + + string DefaultPresetJsonFilePath; + + User CurrentUser; + + Preset CurrentPreset; + + + /// + /// SIMPL+ can only execute the default constructor. If you have variables that require initialization, please + /// use an Initialize method + /// + public WebApiPasscodeClient() + { + } + + /// + /// Initializes the instance + /// + /// + /// + /// + /// + public void Initialize(string key, string jsonMasterKey, string urlBase, string defaultPresetJsonFilePath) + { + Key = key; + //JsonMasterKey = jsonMasterKey; + UrlBase = urlBase; + DefaultPresetJsonFilePath = defaultPresetJsonFilePath; + + J2SMaster = new JsonToSimplGenericMaster(); + J2SMaster.SaveCallback = this.SaveCallback; + J2SMaster.Initialize(jsonMasterKey); + } + + /// + /// Gets the user for a passcode + /// + /// + /// + /// GetUserForPasscode method + /// + public void GetUserForPasscode(string passcode) + { + // Bullshit duplicate code here... These two cases should be the same + // except for https/http and the certificate ignores + if (!UrlBase.StartsWith("https")) + return; + var req = new HttpsClientRequest(); + req.Url = new UrlParser(UrlBase + "/api/users/dopin"); + req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post; + req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json")); + req.Header.AddHeader(new HttpsHeader("Accept", "application/json")); + var jo = new JObject(); + jo.Add("pin", passcode); + req.ContentString = jo.ToString(); + + var client = new HttpsClient(); + client.HostVerification = false; + client.PeerVerification = false; + var resp = client.Dispatch(req); + var handler = UserReceived; + if (resp.Code == 200) + { + //CrestronConsole.PrintLine("Received: {0}", resp.ContentString); + var user = JsonConvert.DeserializeObject(resp.ContentString); + CurrentUser = user; + if (handler != null) + handler(this, new UserReceivedEventArgs(user, true)); + } + else + if (handler != null) + handler(this, new UserReceivedEventArgs(null, false)); + } + + /// + /// + /// + /// + /// + /// + /// GetPresetForThisUser method + /// + public void GetPresetForThisUser(int roomTypeId, int presetNumber) + { + if (CurrentUser == null) + { + CrestronConsole.PrintLine("GetPresetForThisUser no user loaded"); + return; + } + + var msg = new UserAndRoomMessage + { + UserId = CurrentUser.Id, + RoomTypeId = roomTypeId, + PresetNumber = presetNumber + }; + + var handler = PresetReceived; + try + { + if (!UrlBase.StartsWith("https")) + return; + var req = new HttpsClientRequest(); + req.Url = new UrlParser(UrlBase + "/api/presets/userandroom"); + req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post; + req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json")); + req.Header.AddHeader(new HttpsHeader("Accept", "application/json")); + req.ContentString = JsonConvert.SerializeObject(msg); + + var client = new HttpsClient(); + client.HostVerification = false; + client.PeerVerification = false; + + // ask for the preset + var resp = client.Dispatch(req); + if (resp.Code == 200) // got it + { + //Debug.Console(1, this, "Received: {0}", resp.ContentString); + var preset = JsonConvert.DeserializeObject(resp.ContentString); + CurrentPreset = preset; + + //if there's no preset data, load the template + if (preset.Data == null || preset.Data.Trim() == string.Empty || JObject.Parse(preset.Data).Count == 0) + { + //Debug.Console(1, this, "Loaded preset has no data. Loading default template."); + LoadDefaultPresetData(); + return; + } + + J2SMaster.LoadWithJson(preset.Data); + if (handler != null) + handler(this, new PresetReceivedEventArgs(preset, true)); + } + else // no existing preset + { + CurrentPreset = new Preset(); + LoadDefaultPresetData(); + if (handler != null) + handler(this, new PresetReceivedEventArgs(null, false)); + } + } + catch (HttpException e) + { + var resp = e.Response; + Debug.Console(1, this, "No preset received (code {0}). Loading default template", resp.Code); + LoadDefaultPresetData(); + if (handler != null) + PresetReceived(this, new PresetReceivedEventArgs(null, false)); + } + } + + void LoadDefaultPresetData() + { + CurrentPreset = null; + if (!File.Exists(DefaultPresetJsonFilePath)) + { + Debug.Console(0, this, "Cannot load default preset file. Saving will not work"); + return; + } + using (StreamReader sr = new StreamReader(DefaultPresetJsonFilePath)) + { + try + { + var data = sr.ReadToEnd(); + J2SMaster.SetJsonWithoutEvaluating(data); + CurrentPreset = new Preset() { Data = data, UserId = CurrentUser.Id }; + } + catch (Exception e) + { + Debug.Console(0, this, "Error reading default preset JSON: \r{0}", e); + } + } + } + + /// + /// + /// + /// + /// + /// + /// SavePresetForThisUser method + /// + public void SavePresetForThisUser(int roomTypeId, int presetNumber) + { + if (CurrentPreset == null) + LoadDefaultPresetData(); + //return; + + //// A new preset needs to have its numbers set + //if (CurrentPreset.IsNewPreset) + //{ + CurrentPreset.UserId = CurrentUser.Id; + CurrentPreset.RoomTypeId = roomTypeId; + CurrentPreset.PresetNumber = presetNumber; + //} + J2SMaster.Save(); // Will trigger callback when ready + } + + /// + /// After save operation on JSON master happens, send it to server + /// + /// + void SaveCallback(string json) + { + CurrentPreset.Data = json; + + if (!UrlBase.StartsWith("https")) + return; + var req = new HttpsClientRequest(); + req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post; + req.Url = new UrlParser(string.Format("{0}/api/presets/addorchange", UrlBase)); + req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json")); + req.Header.AddHeader(new HttpsHeader("Accept", "application/json")); + req.ContentString = JsonConvert.SerializeObject(CurrentPreset); + + var client = new HttpsClient(); + client.HostVerification = false; + client.PeerVerification = false; + try + { + var resp = client.Dispatch(req); + + // 201=created + // 204=empty content + if (resp.Code == 201) + CrestronConsole.PrintLine("Preset added"); + else if (resp.Code == 204) + CrestronConsole.PrintLine("Preset updated"); + else if (resp.Code == 209) + CrestronConsole.PrintLine("Preset already exists. Cannot save as new."); + else + CrestronConsole.PrintLine("Preset save failed: {0}\r", resp.Code, resp.ContentString); + } + catch (HttpException e) + { + + CrestronConsole.PrintLine("Preset save exception {0}", e.Response.Code); + } + } + } +} diff --git a/src/PepperDash.Core/XSigUtility/Serialization/IXSigSerialization.cs b/src/PepperDash.Core/XSigUtility/Serialization/IXSigSerialization.cs index b1dcae6b..8303731e 100644 --- a/src/PepperDash.Core/XSigUtility/Serialization/IXSigSerialization.cs +++ b/src/PepperDash.Core/XSigUtility/Serialization/IXSigSerialization.cs @@ -1,24 +1,25 @@ using System.Collections.Generic; using PepperDash.Core.Intersystem.Tokens; -namespace PepperDash.Core.Intersystem.Serialization; - -/// -/// Interface to determine XSig serialization for an object. -/// -public interface IXSigSerialization +namespace PepperDash.Core.Intersystem.Serialization { /// - /// Serialize the sig data + /// Interface to determine XSig serialization for an object. /// - /// - IEnumerable Serialize(); + public interface IXSigSerialization + { + /// + /// Serialize the sig data + /// + /// + IEnumerable Serialize(); - /// - /// Deserialize the sig data - /// - /// - /// - /// - T Deserialize(IEnumerable tokens) where T : class, IXSigSerialization; + /// + /// Deserialize the sig data + /// + /// + /// + /// + T Deserialize(IEnumerable tokens) where T : class, IXSigSerialization; + } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Serialization/XSigSerializationException.cs b/src/PepperDash.Core/XSigUtility/Serialization/XSigSerializationException.cs index 4db0e970..8f3fc047 100644 --- a/src/PepperDash.Core/XSigUtility/Serialization/XSigSerializationException.cs +++ b/src/PepperDash.Core/XSigUtility/Serialization/XSigSerializationException.cs @@ -1,27 +1,28 @@ using System; -namespace PepperDash.Core.Intersystem.Serialization; - -/// -/// Class to handle this specific exception type -/// -public class XSigSerializationException : Exception +namespace PepperDash.Core.Intersystem.Serialization { /// - /// default constructor + /// Class to handle this specific exception type /// - public XSigSerializationException() { } + public class XSigSerializationException : Exception + { + /// + /// default constructor + /// + public XSigSerializationException() { } - /// - /// constructor with message - /// - /// - public XSigSerializationException(string message) : base(message) { } + /// + /// constructor with message + /// + /// + public XSigSerializationException(string message) : base(message) { } - /// - /// constructor with message and innner exception - /// - /// - /// - public XSigSerializationException(string message, Exception inner) : base(message, inner) { } + /// + /// constructor with message and innner exception + /// + /// + /// + public XSigSerializationException(string message, Exception inner) : base(message, inner) { } + } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Tokens/XSigAnalogToken.cs b/src/PepperDash.Core/XSigUtility/Tokens/XSigAnalogToken.cs index b5cb2e68..68c61d90 100644 --- a/src/PepperDash.Core/XSigUtility/Tokens/XSigAnalogToken.cs +++ b/src/PepperDash.Core/XSigUtility/Tokens/XSigAnalogToken.cs @@ -1,87 +1,98 @@ using System; -namespace PepperDash.Core.Intersystem.Tokens; - -/// -/// Represents an XSigAnalogToken -/// -public sealed class XSigAnalogToken : XSigToken, IFormattable +namespace PepperDash.Core.Intersystem.Tokens { - private readonly ushort _value; - /// - /// Constructor + /// Represents an XSigAnalogToken /// - /// - /// - public XSigAnalogToken(int index, ushort value) - : base(index) + public sealed class XSigAnalogToken : XSigToken, IFormattable { - // 10-bits available for analog encoded data - if (index >= 1024 || index < 0) - throw new ArgumentOutOfRangeException("index"); + private readonly ushort _value; - _value = value; - } + /// + /// Constructor + /// + /// + /// + public XSigAnalogToken(int index, ushort value) + : base(index) + { + // 10-bits available for analog encoded data + if (index >= 1024 || index < 0) + throw new ArgumentOutOfRangeException("index"); - /// - /// - /// - public ushort Value - { - get { return _value; } - } + _value = value; + } - /// - /// - /// - public override XSigTokenType TokenType - { - get { return XSigTokenType.Analog; } - } + /// + /// + /// + public ushort Value + { + get { return _value; } + } - /// - /// - /// - /// - public override byte[] GetBytes() - { - return new[] { - (byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)), - (byte)((Index - 1) & 0x7F), - (byte)((Value & 0x3F80) >> 7), - (byte)(Value & 0x7F) - }; - } + /// + /// + /// + public override XSigTokenType TokenType + { + get { return XSigTokenType.Analog; } + } - /// - /// - /// - /// - /// - public override XSigToken GetTokenWithOffset(int offset) - { - if (offset == 0) return this; - return new XSigAnalogToken(Index + offset, Value); - } + /// + /// + /// + /// + public override byte[] GetBytes() + { + return new[] { + (byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)), + (byte)((Index - 1) & 0x7F), + (byte)((Value & 0x3F80) >> 7), + (byte)(Value & 0x7F) + }; + } - /// - /// - /// - /// - public override string ToString() - { - return Index + " = 0x" + Value.ToString("X4"); - } + /// + /// + /// + /// + /// + /// + /// GetTokenWithOffset method + /// + public override XSigToken GetTokenWithOffset(int offset) + { + if (offset == 0) return this; + return new XSigAnalogToken(Index + offset, Value); + } - /// - /// - /// - /// - /// - /// - public string ToString(string format, IFormatProvider formatProvider) - { - return Value.ToString(format, formatProvider); + /// + /// + /// + /// + /// + /// ToString method + /// + /// + public override string ToString() + { + return Index + " = 0x" + Value.ToString("X4"); + } + + /// + /// + /// + /// + /// + /// + /// + /// ToString method + /// + public string ToString(string format, IFormatProvider formatProvider) + { + return Value.ToString(format, formatProvider); + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Tokens/XSigDigitalToken.cs b/src/PepperDash.Core/XSigUtility/Tokens/XSigDigitalToken.cs index a82d0eb2..50fac7fc 100644 --- a/src/PepperDash.Core/XSigUtility/Tokens/XSigDigitalToken.cs +++ b/src/PepperDash.Core/XSigUtility/Tokens/XSigDigitalToken.cs @@ -1,84 +1,95 @@ using System; -namespace PepperDash.Core.Intersystem.Tokens; - -/// -/// Represents an XSigDigitalToken -/// -public sealed class XSigDigitalToken : XSigToken +namespace PepperDash.Core.Intersystem.Tokens { - private readonly bool _value; - /// - /// + /// Represents an XSigDigitalToken /// - /// - /// - public XSigDigitalToken(int index, bool value) - : base(index) + public sealed class XSigDigitalToken : XSigToken { - // 12-bits available for digital encoded data - if (index >= 4096 || index < 0) - throw new ArgumentOutOfRangeException("index"); + private readonly bool _value; - _value = value; - } + /// + /// + /// + /// + /// + public XSigDigitalToken(int index, bool value) + : base(index) + { + // 12-bits available for digital encoded data + if (index >= 4096 || index < 0) + throw new ArgumentOutOfRangeException("index"); - /// - /// - /// - public bool Value - { - get { return _value; } - } + _value = value; + } - /// - /// - /// - public override XSigTokenType TokenType - { - get { return XSigTokenType.Digital; } - } + /// + /// + /// + public bool Value + { + get { return _value; } + } - /// - /// - /// - /// - public override byte[] GetBytes() - { - return new[] { - (byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)), - (byte)((Index - 1) & 0x7F) - }; - } + /// + /// + /// + public override XSigTokenType TokenType + { + get { return XSigTokenType.Digital; } + } - /// - /// - /// - /// - /// - public override XSigToken GetTokenWithOffset(int offset) - { - if (offset == 0) return this; - return new XSigDigitalToken(Index + offset, Value); - } + /// + /// + /// + /// + public override byte[] GetBytes() + { + return new[] { + (byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)), + (byte)((Index - 1) & 0x7F) + }; + } - /// - /// - /// - /// - public override string ToString() - { - return Index + " = " + (Value ? "High" : "Low"); - } + /// + /// + /// + /// + /// + /// + /// GetTokenWithOffset method + /// + public override XSigToken GetTokenWithOffset(int offset) + { + if (offset == 0) return this; + return new XSigDigitalToken(Index + offset, Value); + } - /// - /// - /// - /// - /// - public string ToString(IFormatProvider formatProvider) - { - return Value.ToString(formatProvider); + /// + /// + /// + /// + /// + /// ToString method + /// + /// + public override string ToString() + { + return Index + " = " + (Value ? "High" : "Low"); + } + + /// + /// + /// + /// + /// + /// + /// ToString method + /// + public string ToString(IFormatProvider formatProvider) + { + return Value.ToString(formatProvider); + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Tokens/XSigSerialToken.cs b/src/PepperDash.Core/XSigUtility/Tokens/XSigSerialToken.cs index 3b6a3f5f..635d40e3 100644 --- a/src/PepperDash.Core/XSigUtility/Tokens/XSigSerialToken.cs +++ b/src/PepperDash.Core/XSigUtility/Tokens/XSigSerialToken.cs @@ -1,80 +1,88 @@ using System; using System.Text; -namespace PepperDash.Core.Intersystem.Tokens; - -/// -/// Represents an XSigSerialToken -/// -public sealed class XSigSerialToken : XSigToken +namespace PepperDash.Core.Intersystem.Tokens { - private readonly string _value; - /// - /// Constructor + /// Represents an XSigSerialToken /// - /// - /// - public XSigSerialToken(int index, string value) - : base(index) + public sealed class XSigSerialToken : XSigToken { - // 10-bits available for serial encoded data - if (index >= 1024 || index < 0) - throw new ArgumentOutOfRangeException("index"); + private readonly string _value; - _value = value; - } + /// + /// Constructor + /// + /// + /// + public XSigSerialToken(int index, string value) + : base(index) + { + // 10-bits available for serial encoded data + if (index >= 1024 || index < 0) + throw new ArgumentOutOfRangeException("index"); - /// - /// - /// - public string Value - { - get { return _value; } - } + _value = value; + } - /// - /// - /// - public override XSigTokenType TokenType - { - get { return XSigTokenType.Serial; } - } + /// + /// + /// + public string Value + { + get { return _value; } + } - /// - /// - /// - /// - public override byte[] GetBytes() - { - var serialBytes = String.IsNullOrEmpty(Value) ? new byte[0] : Encoding.GetEncoding(28591).GetBytes(Value); - - var xsig = new byte[serialBytes.Length + 3]; - xsig[0] = (byte)(0xC8 | (Index - 1 >> 7)); - xsig[1] = (byte)((Index - 1) & 0x7F); - xsig[xsig.Length - 1] = 0xFF; + /// + /// + /// + public override XSigTokenType TokenType + { + get { return XSigTokenType.Serial; } + } - Buffer.BlockCopy(serialBytes, 0, xsig, 2, serialBytes.Length); - return xsig; - } + /// + /// + /// + /// + public override byte[] GetBytes() + { + var serialBytes = String.IsNullOrEmpty(Value) ? new byte[0] : Encoding.GetEncoding(28591).GetBytes(Value); + + var xsig = new byte[serialBytes.Length + 3]; + xsig[0] = (byte)(0xC8 | (Index - 1 >> 7)); + xsig[1] = (byte)((Index - 1) & 0x7F); + xsig[xsig.Length - 1] = 0xFF; - /// - /// - /// - /// - /// - public override XSigToken GetTokenWithOffset(int offset) - { - if (offset == 0) return this; - return new XSigSerialToken(Index + offset, Value); - } + Buffer.BlockCopy(serialBytes, 0, xsig, 2, serialBytes.Length); + return xsig; + } - /// - /// - /// - /// - public override string ToString() - { - return Index + " = \"" + Value + "\""; + /// + /// + /// + /// + /// + /// + /// GetTokenWithOffset method + /// + public override XSigToken GetTokenWithOffset(int offset) + { + if (offset == 0) return this; + return new XSigSerialToken(Index + offset, Value); + } + + /// + /// + /// + /// + /// + /// ToString method + /// + /// + public override string ToString() + { + return Index + " = \"" + Value + "\""; + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Tokens/XSigToken.cs b/src/PepperDash.Core/XSigUtility/Tokens/XSigToken.cs index cd706bd4..4c00a2ed 100644 --- a/src/PepperDash.Core/XSigUtility/Tokens/XSigToken.cs +++ b/src/PepperDash.Core/XSigUtility/Tokens/XSigToken.cs @@ -1,44 +1,45 @@ -namespace PepperDash.Core.Intersystem.Tokens; - -/// -/// Represents the base class for all XSig datatypes. -/// -public abstract class XSigToken +namespace PepperDash.Core.Intersystem.Tokens { - private readonly int _index; - /// - /// Constructs an XSigToken with the specified index. + /// Represents the base class for all XSig datatypes. /// - /// Index for the data. - protected XSigToken(int index) + public abstract class XSigToken { - _index = index; + private readonly int _index; + + /// + /// Constructs an XSigToken with the specified index. + /// + /// Index for the data. + protected XSigToken(int index) + { + _index = index; + } + + /// + /// XSig 1-based index. + /// + public int Index + { + get { return _index; } + } + + /// + /// XSigToken type. + /// + public abstract XSigTokenType TokenType { get; } + + /// + /// Generates the XSig bytes for the corresponding token. + /// + /// XSig byte array. + public abstract byte[] GetBytes(); + + /// + /// Returns a new token if necessary with an updated index based on the specified offset. + /// + /// Offset to adjust the index with. + /// XSigToken + public abstract XSigToken GetTokenWithOffset(int offset); } - - /// - /// XSig 1-based index. - /// - public int Index - { - get { return _index; } - } - - /// - /// XSigToken type. - /// - public abstract XSigTokenType TokenType { get; } - - /// - /// Generates the XSig bytes for the corresponding token. - /// - /// XSig byte array. - public abstract byte[] GetBytes(); - - /// - /// Returns a new token if necessary with an updated index based on the specified offset. - /// - /// Offset to adjust the index with. - /// XSigToken - public abstract XSigToken GetTokenWithOffset(int offset); } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/Tokens/XSigTokenType.cs b/src/PepperDash.Core/XSigUtility/Tokens/XSigTokenType.cs index 60641b4f..26d6c123 100644 --- a/src/PepperDash.Core/XSigUtility/Tokens/XSigTokenType.cs +++ b/src/PepperDash.Core/XSigUtility/Tokens/XSigTokenType.cs @@ -1,22 +1,23 @@ -namespace PepperDash.Core.Intersystem.Tokens; - -/// -/// XSig token types. -/// -public enum XSigTokenType +namespace PepperDash.Core.Intersystem.Tokens { /// - /// Digital signal datatype. + /// XSig token types. /// - Digital, + public enum XSigTokenType + { + /// + /// Digital signal datatype. + /// + Digital, - /// - /// Analog signal datatype. - /// - Analog, + /// + /// Analog signal datatype. + /// + Analog, - /// - /// Serial signal datatype. - /// - Serial + /// + /// Serial signal datatype. + /// + Serial + } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/XSigHelpers.cs b/src/PepperDash.Core/XSigUtility/XSigHelpers.cs index 4f970be2..d93cc32b 100644 --- a/src/PepperDash.Core/XSigUtility/XSigHelpers.cs +++ b/src/PepperDash.Core/XSigUtility/XSigHelpers.cs @@ -18,221 +18,264 @@ using PepperDash.Core.Intersystem.Tokens; 11111111 <- denotes end of data */ -namespace PepperDash.Core.Intersystem; - -/// -/// Helper methods for creating XSig byte sequences compatible with the Intersystem Communications (ISC) symbol. -/// -/// -/// Indexing is not from the start of each signal type but rather from the beginning of the first defined signal -/// the Intersystem Communications (ISC) symbol. -/// -public static class XSigHelpers +namespace PepperDash.Core.Intersystem { /// - /// Forces all outputs to 0. + /// Helper methods for creating XSig byte sequences compatible with the Intersystem Communications (ISC) symbol. /// - /// Bytes in XSig format for clear outputs trigger. - public static byte[] ClearOutputs() + /// + /// Indexing is not from the start of each signal type but rather from the beginning of the first defined signal + /// the Intersystem Communications (ISC) symbol. + /// + public static class XSigHelpers { - return new byte[] { 0xFC }; - } - - /// - /// Evaluate all inputs and re-transmit any digital, analog, and permanent serail signals not set to 0. - /// - /// Bytes in XSig format for send status trigger. - public static byte[] SendStatus() - { - return new byte[] { 0xFD }; - } - - /// - /// Get bytes for an IXSigStateResolver object. - /// - /// XSig state resolver. - /// Bytes in XSig format for each token within the state representation. - public static byte[] GetBytes(IXSigSerialization xSigSerialization) - { - return GetBytes(xSigSerialization, 0); - } - - /// - /// Get bytes for an IXSigStateResolver object, with a specified offset. - /// - /// XSig state resolver. - /// Offset to which the data will be aligned. - /// Bytes in XSig format for each token within the state representation. - public static byte[] GetBytes(IXSigSerialization xSigSerialization, int offset) - { - var tokens = xSigSerialization.Serialize(); - if (tokens == null) return new byte[0]; - using (var memoryStream = new MemoryStream()) + /// + /// Forces all outputs to 0. + /// + /// Bytes in XSig format for clear outputs trigger. + public static byte[] ClearOutputs() { - using (var tokenWriter = new XSigTokenStreamWriter(memoryStream)) - tokenWriter.WriteXSigData(xSigSerialization, offset); - - return memoryStream.ToArray(); - } - } - - /// - /// Get bytes for a single digital signal. - /// - /// 1-based digital index - /// Digital data to be encoded - /// Bytes in XSig format for digtial information. - public static byte[] GetBytes(int index, bool value) - { - return GetBytes(index, 0, value); - } - - /// - /// Get bytes for a single digital signal. - /// - /// 1-based digital index - /// Index offset. - /// Digital data to be encoded - /// Bytes in XSig format for digtial information. - public static byte[] GetBytes(int index, int offset, bool value) - { - return new XSigDigitalToken(index + offset, value).GetBytes(); - } - - /// - /// Get byte sequence for multiple digital signals. - /// - /// Starting index of the sequence. - /// Digital signal value array. - /// Byte sequence in XSig format for digital signal information. - public static byte[] GetBytes(int startIndex, bool[] values) - { - return GetBytes(startIndex, 0, values); - } - - /// - /// Get byte sequence for multiple digital signals. - /// - /// Starting index of the sequence. - /// Index offset. - /// Digital signal value array. - /// Byte sequence in XSig format for digital signal information. - public static byte[] GetBytes(int startIndex, int offset, bool[] values) - { - // Digital XSig data is 2 bytes per value - const int fixedLength = 2; - var bytes = new byte[values.Length * fixedLength]; - for (var i = 0; i < values.Length; i++) - Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength); - - return bytes; - } - - /// - /// Get bytes for a single analog signal. - /// - /// 1-based analog index - /// Analog data to be encoded - /// Bytes in XSig format for analog signal information. - public static byte[] GetBytes(int index, ushort value) - { - return GetBytes(index, 0, value); - } - - /// - /// Get bytes for a single analog signal. - /// - /// 1-based analog index - /// Index offset. - /// Analog data to be encoded - /// Bytes in XSig format for analog signal information. - public static byte[] GetBytes(int index, int offset, ushort value) - { - return new XSigAnalogToken(index + offset, value).GetBytes(); - } - - /// - /// Get byte sequence for multiple analog signals. - /// - /// Starting index of the sequence. - /// Analog signal value array. - /// Byte sequence in XSig format for analog signal information. - public static byte[] GetBytes(int startIndex, ushort[] values) - { - return GetBytes(startIndex, 0, values); - } - - /// - /// Get byte sequence for multiple analog signals. - /// - /// Starting index of the sequence. - /// Index offset. - /// Analog signal value array. - /// Byte sequence in XSig format for analog signal information. - public static byte[] GetBytes(int startIndex, int offset, ushort[] values) - { - // Analog XSig data is 4 bytes per value - const int fixedLength = 4; - var bytes = new byte[values.Length * fixedLength]; - for (var i = 0; i < values.Length; i++) - Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength); - - return bytes; - } - - /// - /// Get bytes for a single serial signal. - /// - /// 1-based serial index - /// Serial data to be encoded - /// Bytes in XSig format for serial signal information. - public static byte[] GetBytes(int index, string value) - { - return GetBytes(index, 0, value); - } - - /// - /// Get bytes for a single serial signal. - /// - /// 1-based serial index - /// Index offset. - /// Serial data to be encoded - /// Bytes in XSig format for serial signal information. - public static byte[] GetBytes(int index, int offset, string value) - { - return new XSigSerialToken(index + offset, value).GetBytes(); - } - - /// - /// Get byte sequence for multiple serial signals. - /// - /// Starting index of the sequence. - /// Serial signal value array. - /// Byte sequence in XSig format for serial signal information. - public static byte[] GetBytes(int startIndex, string[] values) - { - return GetBytes(startIndex, 0, values); - } - - /// - /// Get byte sequence for multiple serial signals. - /// - /// Starting index of the sequence. - /// Index offset. - /// Serial signal value array. - /// Byte sequence in XSig format for serial signal information. - public static byte[] GetBytes(int startIndex, int offset, string[] values) - { - // Serial XSig data is not fixed-length like the other formats - var dstOffset = 0; - var bytes = new byte[values.Sum(v => v.Length + 3)]; - for (var i = 0; i < values.Length; i++) - { - var data = GetBytes(startIndex++, offset, values[i]); - Buffer.BlockCopy(data, 0, bytes, dstOffset, data.Length); - dstOffset += data.Length; + return new byte[] { 0xFC }; } - return bytes; + /// + /// Evaluate all inputs and re-transmit any digital, analog, and permanent serail signals not set to 0. + /// + /// Bytes in XSig format for send status trigger. + public static byte[] SendStatus() + { + return new byte[] { 0xFD }; + } + + /// + /// Get bytes for an IXSigStateResolver object. + /// + /// XSig state resolver. + /// Bytes in XSig format for each token within the state representation. + /// + /// GetBytes method + /// + public static byte[] GetBytes(IXSigSerialization xSigSerialization) + { + return GetBytes(xSigSerialization, 0); + } + + /// + /// Get bytes for an IXSigStateResolver object, with a specified offset. + /// + /// XSig state resolver. + /// Offset to which the data will be aligned. + /// Bytes in XSig format for each token within the state representation. + /// + /// GetBytes method + /// + public static byte[] GetBytes(IXSigSerialization xSigSerialization, int offset) + { + var tokens = xSigSerialization.Serialize(); + if (tokens == null) return new byte[0]; + using (var memoryStream = new MemoryStream()) + { + using (var tokenWriter = new XSigTokenStreamWriter(memoryStream)) + tokenWriter.WriteXSigData(xSigSerialization, offset); + + return memoryStream.ToArray(); + } + } + + /// + /// Get bytes for a single digital signal. + /// + /// 1-based digital index + /// Digital data to be encoded + /// Bytes in XSig format for digtial information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, bool value) + { + return GetBytes(index, 0, value); + } + + /// + /// Get bytes for a single digital signal. + /// + /// 1-based digital index + /// Index offset. + /// Digital data to be encoded + /// Bytes in XSig format for digtial information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, int offset, bool value) + { + return new XSigDigitalToken(index + offset, value).GetBytes(); + } + + /// + /// Get byte sequence for multiple digital signals. + /// + /// Starting index of the sequence. + /// Digital signal value array. + /// Byte sequence in XSig format for digital signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, bool[] values) + { + return GetBytes(startIndex, 0, values); + } + + /// + /// Get byte sequence for multiple digital signals. + /// + /// Starting index of the sequence. + /// Index offset. + /// Digital signal value array. + /// Byte sequence in XSig format for digital signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, int offset, bool[] values) + { + // Digital XSig data is 2 bytes per value + const int fixedLength = 2; + var bytes = new byte[values.Length * fixedLength]; + for (var i = 0; i < values.Length; i++) + Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength); + + return bytes; + } + + /// + /// Get bytes for a single analog signal. + /// + /// 1-based analog index + /// Analog data to be encoded + /// Bytes in XSig format for analog signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, ushort value) + { + return GetBytes(index, 0, value); + } + + /// + /// Get bytes for a single analog signal. + /// + /// 1-based analog index + /// Index offset. + /// Analog data to be encoded + /// Bytes in XSig format for analog signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, int offset, ushort value) + { + return new XSigAnalogToken(index + offset, value).GetBytes(); + } + + /// + /// Get byte sequence for multiple analog signals. + /// + /// Starting index of the sequence. + /// Analog signal value array. + /// Byte sequence in XSig format for analog signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, ushort[] values) + { + return GetBytes(startIndex, 0, values); + } + + /// + /// Get byte sequence for multiple analog signals. + /// + /// Starting index of the sequence. + /// Index offset. + /// Analog signal value array. + /// Byte sequence in XSig format for analog signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, int offset, ushort[] values) + { + // Analog XSig data is 4 bytes per value + const int fixedLength = 4; + var bytes = new byte[values.Length * fixedLength]; + for (var i = 0; i < values.Length; i++) + Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength); + + return bytes; + } + + /// + /// Get bytes for a single serial signal. + /// + /// 1-based serial index + /// Serial data to be encoded + /// Bytes in XSig format for serial signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, string value) + { + return GetBytes(index, 0, value); + } + + /// + /// Get bytes for a single serial signal. + /// + /// 1-based serial index + /// Index offset. + /// Serial data to be encoded + /// Bytes in XSig format for serial signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int index, int offset, string value) + { + return new XSigSerialToken(index + offset, value).GetBytes(); + } + + /// + /// Get byte sequence for multiple serial signals. + /// + /// Starting index of the sequence. + /// Serial signal value array. + /// Byte sequence in XSig format for serial signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, string[] values) + { + return GetBytes(startIndex, 0, values); + } + + /// + /// Get byte sequence for multiple serial signals. + /// + /// Starting index of the sequence. + /// Index offset. + /// Serial signal value array. + /// Byte sequence in XSig format for serial signal information. + /// + /// GetBytes method + /// + public static byte[] GetBytes(int startIndex, int offset, string[] values) + { + // Serial XSig data is not fixed-length like the other formats + var dstOffset = 0; + var bytes = new byte[values.Sum(v => v.Length + 3)]; + for (var i = 0; i < values.Length; i++) + { + var data = GetBytes(startIndex++, offset, values[i]); + Buffer.BlockCopy(data, 0, bytes, dstOffset, data.Length); + dstOffset += data.Length; + } + + return bytes; + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/XSigTokenStreamReader.cs b/src/PepperDash.Core/XSigUtility/XSigTokenStreamReader.cs index 3c222960..03e79946 100644 --- a/src/PepperDash.Core/XSigUtility/XSigTokenStreamReader.cs +++ b/src/PepperDash.Core/XSigUtility/XSigTokenStreamReader.cs @@ -4,143 +4,153 @@ using Crestron.SimplSharp.CrestronIO; using PepperDash.Core.Intersystem.Serialization; using PepperDash.Core.Intersystem.Tokens; -namespace PepperDash.Core.Intersystem; - -/// -/// XSigToken stream reader. -/// -public sealed class XSigTokenStreamReader : IDisposable +namespace PepperDash.Core.Intersystem { - private readonly Stream _stream; - private readonly bool _leaveOpen; - - /// /// - /// XSigToken stream reader constructor. + /// XSigToken stream reader. /// - /// Input stream to read from. - /// Stream is null. - /// Stream cannot be read from. - public XSigTokenStreamReader(Stream stream) - : this(stream, false) { } - - /// - /// XSigToken stream reader constructor. - /// - /// Input stream to read from. - /// Determines whether to leave the stream open or not. - /// Stream is null. - /// Stream cannot be read from. - public XSigTokenStreamReader(Stream stream, bool leaveOpen) + public sealed class XSigTokenStreamReader : IDisposable { - if (stream == null) - throw new ArgumentNullException("stream"); - if (!stream.CanRead) - throw new ArgumentException("The specified stream cannot be read from."); + private readonly Stream _stream; + private readonly bool _leaveOpen; - _stream = stream; - _leaveOpen = leaveOpen; - } + /// + /// + /// XSigToken stream reader constructor. + /// + /// Input stream to read from. + /// Stream is null. + /// Stream cannot be read from. + public XSigTokenStreamReader(Stream stream) + : this(stream, false) { } - /// - /// Reads a 16-bit unsigned integer from the specified stream using Big Endian byte order. - /// - /// Input stream - /// Result - /// True if successful, otherwise false. - public static bool TryReadUInt16BE(Stream stream, out ushort value) - { - value = 0; - if (stream.Length < 2) - return false; - - var buffer = new byte[2]; - stream.Read(buffer, 0, 2); - value = (ushort)((buffer[0] << 8) | buffer[1]); - return true; - } - - /// - /// Read XSig token from the stream. - /// - /// XSigToken - /// Offset is less than 0. - public XSigToken ReadXSigToken() - { - ushort prefix; - if (!TryReadUInt16BE(_stream, out prefix)) - return null; - - if ((prefix & 0xF880) == 0xC800) // Serial data + /// + /// XSigToken stream reader constructor. + /// + /// Input stream to read from. + /// Determines whether to leave the stream open or not. + /// Stream is null. + /// Stream cannot be read from. + public XSigTokenStreamReader(Stream stream, bool leaveOpen) { - var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F); - var n = 0; - const int maxSerialDataLength = 252; - var chars = new char[maxSerialDataLength]; - int ch; - while ((ch = _stream.ReadByte()) != 0xFF) - { - if (ch == -1) // Reached end of stream without end of data marker - return null; - - chars[n++] = (char)ch; - } + if (stream == null) + throw new ArgumentNullException("stream"); + if (!stream.CanRead) + throw new ArgumentException("The specified stream cannot be read from."); - return new XSigSerialToken((ushort)(index + 1), new string(chars, 0, n)); + _stream = stream; + _leaveOpen = leaveOpen; } - if ((prefix & 0xC880) == 0xC000) // Analog data + /// + /// Reads a 16-bit unsigned integer from the specified stream using Big Endian byte order. + /// + /// Input stream + /// Result + /// True if successful, otherwise false. + /// + /// TryReadUInt16BE method + /// + public static bool TryReadUInt16BE(Stream stream, out ushort value) { - ushort data; - if (!TryReadUInt16BE(_stream, out data)) + value = 0; + if (stream.Length < 2) + return false; + + var buffer = new byte[2]; + stream.Read(buffer, 0, 2); + value = (ushort)((buffer[0] << 8) | buffer[1]); + return true; + } + + /// + /// Read XSig token from the stream. + /// + /// XSigToken + /// Offset is less than 0. + /// + /// ReadXSigToken method + /// + public XSigToken ReadXSigToken() + { + ushort prefix; + if (!TryReadUInt16BE(_stream, out prefix)) return null; - var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F); - var value = ((prefix & 0x3000) << 2) | ((data & 0x7F00) >> 1) | (data & 0x7F); - return new XSigAnalogToken((ushort)(index + 1), (ushort)value); + if ((prefix & 0xF880) == 0xC800) // Serial data + { + var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F); + var n = 0; + const int maxSerialDataLength = 252; + var chars = new char[maxSerialDataLength]; + int ch; + while ((ch = _stream.ReadByte()) != 0xFF) + { + if (ch == -1) // Reached end of stream without end of data marker + return null; + + chars[n++] = (char)ch; + } + + return new XSigSerialToken((ushort)(index + 1), new string(chars, 0, n)); + } + + if ((prefix & 0xC880) == 0xC000) // Analog data + { + ushort data; + if (!TryReadUInt16BE(_stream, out data)) + return null; + + var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F); + var value = ((prefix & 0x3000) << 2) | ((data & 0x7F00) >> 1) | (data & 0x7F); + return new XSigAnalogToken((ushort)(index + 1), (ushort)value); + } + + if ((prefix & 0xC080) == 0x8000) // Digital data + { + var index = ((prefix & 0x1F00) >> 1) | (prefix & 0x7F); + var value = (prefix & 0x2000) == 0; + return new XSigDigitalToken((ushort)(index + 1), value); + } + + return null; } - if ((prefix & 0xC080) == 0x8000) // Digital data + /// + /// Reads all available XSig tokens from the stream. + /// + /// XSigToken collection. + /// + /// ReadAllXSigTokens method + /// + public IEnumerable ReadAllXSigTokens() { - var index = ((prefix & 0x1F00) >> 1) | (prefix & 0x7F); - var value = (prefix & 0x2000) == 0; - return new XSigDigitalToken((ushort)(index + 1), value); + var tokens = new List(); + XSigToken token; + while ((token = ReadXSigToken()) != null) + tokens.Add(token); + + return tokens; } - return null; - } + /// + /// Attempts to deserialize all XSig data within the stream from the current position. + /// + /// Type to deserialize the information to. + /// Deserialized object. + public T DeserializeStream() + where T : class, IXSigSerialization, new() + { + return new T().Deserialize(ReadAllXSigTokens()); + } - /// - /// Reads all available XSig tokens from the stream. - /// - /// XSigToken collection. - public IEnumerable ReadAllXSigTokens() - { - var tokens = new List(); - XSigToken token; - while ((token = ReadXSigToken()) != null) - tokens.Add(token); - - return tokens; - } - - /// - /// Attempts to deserialize all XSig data within the stream from the current position. - /// - /// Type to deserialize the information to. - /// Deserialized object. - public T DeserializeStream() - where T : class, IXSigSerialization, new() - { - return new T().Deserialize(ReadAllXSigTokens()); - } - - /// - /// Disposes of the internal stream if specified to not leave open. - /// - public void Dispose() - { - if (!_leaveOpen) - _stream.Dispose(); + /// + /// Disposes of the internal stream if specified to not leave open. + /// + public void Dispose() + { + if (!_leaveOpen) + _stream.Dispose(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Core/XSigUtility/XSigTokenStreamWriter.cs b/src/PepperDash.Core/XSigUtility/XSigTokenStreamWriter.cs index da973acd..12eeaf91 100644 --- a/src/PepperDash.Core/XSigUtility/XSigTokenStreamWriter.cs +++ b/src/PepperDash.Core/XSigUtility/XSigTokenStreamWriter.cs @@ -5,131 +5,147 @@ using Crestron.SimplSharp.CrestronIO; using PepperDash.Core.Intersystem.Serialization; using PepperDash.Core.Intersystem.Tokens; -namespace PepperDash.Core.Intersystem; - -/// -/// XSigToken stream writer. -/// -public sealed class XSigTokenStreamWriter : IDisposable +namespace PepperDash.Core.Intersystem { - private readonly Stream _stream; - private readonly bool _leaveOpen; - - /// /// - /// XSigToken stream writer constructor. + /// XSigToken stream writer. /// - /// Input stream to write to. - /// Stream is null. - /// Stream cannot be written to. - public XSigTokenStreamWriter(Stream stream) - : this(stream, false) { } - - /// - /// XSigToken stream writer constructor. - /// - /// Input stream to write to. - /// Determines whether to leave the stream open or not. - /// Stream is null. - /// Stream cannot be written to. - public XSigTokenStreamWriter(Stream stream, bool leaveOpen) + public sealed class XSigTokenStreamWriter : IDisposable { - if (stream == null) - throw new ArgumentNullException("stream"); - if (!stream.CanWrite) - throw new ArgumentException("The specified stream cannot be written to."); + private readonly Stream _stream; + private readonly bool _leaveOpen; - _stream = stream; - _leaveOpen = leaveOpen; - } + /// + /// + /// XSigToken stream writer constructor. + /// + /// Input stream to write to. + /// Stream is null. + /// Stream cannot be written to. + public XSigTokenStreamWriter(Stream stream) + : this(stream, false) { } - /// - /// Write XSig data gathered from an IXSigStateResolver to the stream. - /// - /// IXSigStateResolver object. - public void WriteXSigData(IXSigSerialization xSigSerialization) - { - WriteXSigData(xSigSerialization, 0); - } - - /// - /// Write XSig data gathered from an IXSigStateResolver to the stream. - /// - /// IXSigStateResolver object. - /// Index offset for each XSigToken. - public void WriteXSigData(IXSigSerialization xSigSerialization, int offset) - { - if (xSigSerialization == null) - throw new ArgumentNullException("xSigSerialization"); - - var tokens = xSigSerialization.Serialize(); - WriteXSigData(tokens, offset); - } - - /// - /// Write XSigToken to the stream. - /// - /// XSigToken object. - public void WriteXSigData(XSigToken token) - { - WriteXSigData(token, 0); - } - - /// - /// Write XSigToken to the stream. - /// - /// XSigToken object. - /// Index offset for each XSigToken. - public void WriteXSigData(XSigToken token, int offset) - { - WriteXSigData(new[] { token }, offset); - } - - /// - /// Writes an array of XSigTokens to the stream. - /// - /// XSigToken objects. - public void WriteXSigData(XSigToken[] tokens) - { - WriteXSigData(tokens.AsEnumerable()); - } - - /// - /// Write an enumerable collection of XSigTokens to the stream. - /// - /// XSigToken objects. - public void WriteXSigData(IEnumerable tokens) - { - WriteXSigData(tokens, 0); - } - - /// - /// Write an enumerable collection of XSigTokens to the stream. - /// - /// XSigToken objects. - /// Index offset for each XSigToken. - public void WriteXSigData(IEnumerable tokens, int offset) - { - if (offset < 0) - throw new ArgumentOutOfRangeException("offset", "Offset must be greater than or equal to 0."); - - if (tokens != null) + /// + /// XSigToken stream writer constructor. + /// + /// Input stream to write to. + /// Determines whether to leave the stream open or not. + /// Stream is null. + /// Stream cannot be written to. + public XSigTokenStreamWriter(Stream stream, bool leaveOpen) { - foreach (var token in tokens) + if (stream == null) + throw new ArgumentNullException("stream"); + if (!stream.CanWrite) + throw new ArgumentException("The specified stream cannot be written to."); + + _stream = stream; + _leaveOpen = leaveOpen; + } + + /// + /// Write XSig data gathered from an IXSigStateResolver to the stream. + /// + /// IXSigStateResolver object. + /// + /// WriteXSigData method + /// + public void WriteXSigData(IXSigSerialization xSigSerialization) + { + WriteXSigData(xSigSerialization, 0); + } + + /// + /// Write XSig data gathered from an IXSigStateResolver to the stream. + /// + /// IXSigStateResolver object. + /// Index offset for each XSigToken. + /// + /// WriteXSigData method + /// + public void WriteXSigData(IXSigSerialization xSigSerialization, int offset) + { + if (xSigSerialization == null) + throw new ArgumentNullException("xSigSerialization"); + + var tokens = xSigSerialization.Serialize(); + WriteXSigData(tokens, offset); + } + + /// + /// Write XSigToken to the stream. + /// + /// XSigToken object. + /// + /// WriteXSigData method + /// + public void WriteXSigData(XSigToken token) + { + WriteXSigData(token, 0); + } + + /// + /// Write XSigToken to the stream. + /// + /// XSigToken object. + /// Index offset for each XSigToken. + /// + /// WriteXSigData method + /// + public void WriteXSigData(XSigToken token, int offset) + { + WriteXSigData(new[] { token }, offset); + } + + /// + /// Writes an array of XSigTokens to the stream. + /// + /// XSigToken objects. + public void WriteXSigData(XSigToken[] tokens) + { + WriteXSigData(tokens.AsEnumerable()); + } + + /// + /// Write an enumerable collection of XSigTokens to the stream. + /// + /// XSigToken objects. + public void WriteXSigData(IEnumerable tokens) + { + WriteXSigData(tokens, 0); + } + + /// + /// Write an enumerable collection of XSigTokens to the stream. + /// + /// XSigToken objects. + /// Index offset for each XSigToken. + /// + /// WriteXSigData method + /// + public void WriteXSigData(IEnumerable tokens, int offset) + { + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", "Offset must be greater than or equal to 0."); + + if (tokens != null) { - if (token == null) continue; - var bytes = token.GetTokenWithOffset(offset).GetBytes(); - _stream.Write(bytes, 0, bytes.Length); + foreach (var token in tokens) + { + if (token == null) continue; + var bytes = token.GetTokenWithOffset(offset).GetBytes(); + _stream.Write(bytes, 0, bytes.Length); + } } } - } - /// - /// Disposes of the internal stream if specified to not leave open. - /// - public void Dispose() - { - if (!_leaveOpen) - _stream.Dispose(); + /// + /// Dispose method + /// + public void Dispose() + { + if (!_leaveOpen) + _stream.Dispose(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core.Tests/Config/ConfigServiceFakesTests.cs b/src/PepperDash.Essentials.Core.Tests/Config/ConfigServiceFakesTests.cs deleted file mode 100644 index 1359a450..00000000 --- a/src/PepperDash.Essentials.Core.Tests/Config/ConfigServiceFakesTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using FluentAssertions; -using PepperDash.Core.Abstractions; -using PepperDash.Core.Tests.Fakes; -using Xunit; - -namespace PepperDash.Essentials.Core.Tests.Config; - -/// -/// Tests for the configuration loading abstractions. -/// These verify behaviour of the test fakes and interfaces independently of -/// any Crestron SDK types (ConfigReader itself will be tested here once it -/// is migrated from Crestron.SimplSharp.CrestronIO to System.IO — see plan Phase 4). -/// -public class ConfigServiceFakesTests -{ - [Fact] - public void DataStore_MultipleKeys_AreStoredIndependently() - { - var store = new InMemoryCrestronDataStore(); - store.InitStore(); - - store.SetLocalInt("KeyA", 1); - store.SetLocalInt("KeyB", 2); - - store.TryGetLocalInt("KeyA", out var a); - store.TryGetLocalInt("KeyB", out var b); - - a.Should().Be(1); - b.Should().Be(2); - } - - [Fact] - public void DataStore_OverwriteKey_ReturnsNewValue() - { - var store = new InMemoryCrestronDataStore(); - store.SetLocalInt("Level", 1); - store.SetLocalInt("Level", 5); - - store.TryGetLocalInt("Level", out var level); - level.Should().Be(5); - } - - [Fact] - public void FakeEnvironment_CanBeConfiguredForServer() - { - var env = new FakeCrestronEnvironment - { - DevicePlatform = DevicePlatform.Server, - ApplicationNumber = 1, - RoomId = 42, - }; - - env.DevicePlatform.Should().Be(DevicePlatform.Server); - env.RoomId.Should().Be(42u); - } - - [Fact] - public void FakeEthernetHelper_SeedAndRetrieve() - { - var eth = new FakeEthernetHelper() - .Seed(EthernetParameterType.GetCurrentIpAddress, "192.168.1.100") - .Seed(EthernetParameterType.GetHostname, "MC4-TEST"); - - eth.GetEthernetParameter(EthernetParameterType.GetCurrentIpAddress, 0) - .Should().Be("192.168.1.100"); - - eth.GetEthernetParameter(EthernetParameterType.GetHostname, 0) - .Should().Be("MC4-TEST"); - - eth.GetEthernetParameter(EthernetParameterType.GetDomainName, 0) - .Should().BeEmpty("unseeded parameter should return empty string"); - } -} diff --git a/src/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj b/src/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj deleted file mode 100644 index 62582959..00000000 --- a/src/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - net9.0 - enable - enable - false - false - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - diff --git a/src/PepperDash.Essentials.Core/Bridges/BridgeBase.cs b/src/PepperDash.Essentials.Core/Bridges/BridgeBase.cs index 6adf7d37..66e3ccd8 100644 --- a/src/PepperDash.Essentials.Core/Bridges/BridgeBase.cs +++ b/src/PepperDash.Essentials.Core/Bridges/BridgeBase.cs @@ -13,408 +13,518 @@ using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core.Bridges; - -/// -/// Base class for bridge API variants -/// -public abstract class BridgeApi : EssentialsDevice +namespace PepperDash.Essentials.Core.Bridges { - protected BridgeApi(string key) : - base(key) + /// + /// Base class for bridge API variants + /// + [Obsolete("Will be removed in v3.0.0")] + public abstract class BridgeApi : EssentialsDevice { - - } -} - -/// -/// Bridge API using EISC -/// -public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor -{ - public EiscApiPropertiesConfig PropertiesConfig { get; private set; } - - public Dictionary JoinMaps { get; private set; } - - public BasicTriList Eisc { get; private set; } - - public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) : - base(dc.Key) - { - JoinMaps = new Dictionary(); - - PropertiesConfig = dc.Properties.ToObject(); - //PropertiesConfig = JsonConvert.DeserializeObject(dc.Properties.ToString()); - - Eisc = eisc; - - Eisc.SigChange += Eisc_SigChange; - - CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, Eisc, 120000, 300000); - - AddPostActivationAction(LinkDevices); - AddPostActivationAction(LinkRooms); - AddPostActivationAction(RegisterEisc); - } - - protected override bool CustomActivate() - { - CommunicationMonitor.Start(); - return base.CustomActivate(); - } - - public override bool Deactivate() - { - CommunicationMonitor.Stop(); - return base.Deactivate(); - } - - private void LinkDevices() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Linking Devices..."); - - if (PropertiesConfig.Devices == null) + /// + /// Constructor + /// + /// Device key + protected BridgeApi(string key) : + base(key) { - Debug.LogMessage(LogEventLevel.Debug, this, "No devices linked to this bridge"); - return; + } - foreach (var d in PropertiesConfig.Devices) + /// + /// Constructor + /// + /// + /// + protected BridgeApi(string key, string name) : + base(key, name) { - var device = DeviceManager.GetDeviceForKey(d.DeviceKey); - if (device == null) + } + } + + /// + /// Class to link devices and rooms to an EISC Instance + /// + public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor + { + /// + /// Gets the PropertiesConfig + /// + public EiscApiPropertiesConfig PropertiesConfig { get; private set; } + + /// + /// Gets the JoinMaps dictionary + /// + public Dictionary JoinMaps { get; private set; } + + /// + /// Gets the EISC instance + /// + public BasicTriList Eisc { get; private set; } + + /// + /// Constructor + /// + /// Device configuration + /// EISC instance + public EiscApiAdvanced(DeviceConfig dc, BasicTriList eisc) : + base(dc.Key, dc.Name) + { + JoinMaps = new Dictionary(); + + PropertiesConfig = dc.Properties.ToObject(); + + Eisc = eisc; + + Eisc.SigChange += Eisc_SigChange; + + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, Eisc, 120000, 300000); + + AddPostActivationAction(LinkDevices); + AddPostActivationAction(LinkRooms); + AddPostActivationAction(RegisterEisc); + } + + /// + /// CustomActivate method + /// + public override bool CustomActivate() + { + CommunicationMonitor.Start(); + return base.CustomActivate(); + } + + /// + /// Deactivate method + /// + public override bool Deactivate() + { + CommunicationMonitor.Stop(); + return base.Deactivate(); + } + + private void LinkDevices() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Linking Devices..."); + + if (PropertiesConfig.Devices == null) { - continue; + this.LogDebug("No devices linked to this bridge"); + return; } - Debug.LogMessage(LogEventLevel.Debug, this, "Linking Device: '{0}'", device.Key); - - if (device is IBridgeAdvanced bridge) + foreach (var d in PropertiesConfig.Devices) { - bridge.LinkToApi(Eisc, d.JoinStart, d.JoinMapKey, this); - continue; + var device = DeviceManager.GetDeviceForKey(d.DeviceKey); + + if (device == null) + { + continue; + } + + Debug.LogMessage(LogEventLevel.Debug, this, "Linking Device: '{0}'", device.Key); + + if (device is IBridgeAdvanced bridge) + { + bridge.LinkToApi(Eisc, d.JoinStart, d.JoinMapKey, this); + continue; + } + + this.LogWarning("{deviceKey} is not compatible with this bridge type. Please update the device.", device.Key); + } + } + + private void RegisterEisc() + { + if (Eisc.Registered) + { + return; } - Debug.LogMessage(LogEventLevel.Information, this, - "{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.", - device.Key); - } - } + var registerResult = Eisc.Register(); - private void RegisterEisc() - { - if (Eisc.Registered) - { - return; - } - - var registerResult = Eisc.Register(); - - if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Registration result: {0}", registerResult); - return; - } - - Debug.LogMessage(LogEventLevel.Debug, this, "EISC registration successful"); - } - - public void LinkRooms() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Linking Rooms..."); - - if (PropertiesConfig.Rooms == null) - { - Debug.LogMessage(LogEventLevel.Debug, this, "No rooms linked to this bridge."); - return; - } - - foreach (var room in PropertiesConfig.Rooms) - { - var rm = DeviceManager.GetDeviceForKey(room.RoomKey) as IBridgeAdvanced; - - if (rm == null) + if (registerResult != eDeviceRegistrationUnRegistrationResponse.Success) { - Debug.LogMessage(LogEventLevel.Debug, this, - "Room {0} does not implement IBridgeAdvanced. Skipping...", room.RoomKey); - continue; + this.LogVerbose("Registration result: {registerResult}", registerResult); + return; } - rm.LinkToApi(Eisc, room.JoinStart, room.JoinMapKey, this); - } - } - - /// - /// Adds a join map - /// - /// - /// - public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap) - { - if (!JoinMaps.ContainsKey(deviceKey)) - { - JoinMaps.Add(deviceKey, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to add join map with key '{0}'. Key already exists in JoinMaps dictionary", deviceKey); - } - } - - /// - /// Prints all the join maps on this bridge - /// - public virtual void PrintJoinMaps() - { - CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X")); - - foreach (var joinMap in JoinMaps) - { - CrestronConsole.ConsoleCommandResponse("Join map for device '{0}':", joinMap.Key); - joinMap.Value.PrintJoinMapInfo(); - } - } - /// - /// Generates markdown for all join maps on this bridge - /// - public virtual void MarkdownForBridge(string bridgeKey) - { - Debug.LogMessage(LogEventLevel.Information, this, "Writing Joinmaps to files for EISC IPID: {0}", Eisc.ID.ToString("X")); - - foreach (var joinMap in JoinMaps) - { - Debug.LogMessage(LogEventLevel.Information, "Generating markdown for device '{0}':", joinMap.Key); - joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey); - } - } - - /// - /// Prints the join map for a device by key - /// - /// - public void PrintJoinMapForDevice(string deviceKey) - { - var joinMap = JoinMaps[deviceKey]; - - if (joinMap == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey); - return; + this.LogDebug("EISC registration successful"); } - Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key); - joinMap.PrintJoinMapInfo(); - } - /// - /// Prints the join map for a device by key - /// - /// - public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey) - { - var joinMap = JoinMaps[deviceKey]; - - if (joinMap == null) + /// + /// Link rooms to this EISC. Rooms MUST implement IBridgeAdvanced + /// + public void LinkRooms() { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to find joinMap for device with key: '{0}'", deviceKey); - return; - } + this.LogDebug("Linking Rooms..."); - Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}' on EISC '{1}':", deviceKey, Key); - joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey); - } - - /// - /// Used for debugging to trigger an action based on a join number and type - /// - /// - /// - /// - public void ExecuteJoinAction(uint join, string type, object state) - { - try - { - switch (type.ToLower()) + if (PropertiesConfig.Rooms == null) { - case "digital": - { - var uo = Eisc.BooleanOutput[join].UserObject as Action; - if (uo != null) + this.LogDebug("No rooms linked to this bridge."); + return; + } + + foreach (var room in PropertiesConfig.Rooms) + { + if (!(DeviceManager.GetDeviceForKey(room.RoomKey) is IBridgeAdvanced rm)) + { + this.LogDebug("Room {roomKey} does not implement IBridgeAdvanced. Skipping...", room.RoomKey); + continue; + } + + rm.LinkToApi(Eisc, room.JoinStart, room.JoinMapKey, this); + } + } + + /// + /// Adds a join map + /// + /// The key of the device to add the join map for + /// The join map to add + public void AddJoinMap(string deviceKey, JoinMapBaseAdvanced joinMap) + { + if (!JoinMaps.ContainsKey(deviceKey)) + { + JoinMaps.Add(deviceKey, joinMap); + } + else + { + this.LogWarning("Unable to add join map with key '{deviceKey}'. Key already exists in JoinMaps dictionary", deviceKey); + } + } + + /// + /// PrintJoinMaps method + /// + public virtual void PrintJoinMaps() + { + CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X")); + + foreach (var joinMap in JoinMaps) + { + CrestronConsole.ConsoleCommandResponse("Join map for device '{0}':", joinMap.Key); + joinMap.Value.PrintJoinMapInfo(); + } + } + + /// + /// MarkdownForBridge method + /// + public virtual void MarkdownForBridge(string bridgeKey) + { + this.LogInformation("Writing Joinmaps to files for EISC IPID: {eiscId}", Eisc.ID.ToString("X")); + + foreach (var joinMap in JoinMaps) + { + this.LogInformation("Generating markdown for device '{deviceKey}':", joinMap.Key); + joinMap.Value.MarkdownJoinMapInfo(joinMap.Key, bridgeKey); + } + } + + /// + /// Prints the join map for a device by key + /// + /// The key of the device to print the join map for + public void PrintJoinMapForDevice(string deviceKey) + { + var joinMap = JoinMaps[deviceKey]; + + if (joinMap == null) + { + this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey); + return; + } + + this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key); + joinMap.PrintJoinMapInfo(); + } + /// + /// Prints the join map for a device by key in Markdown format + /// + /// The key of the device to print the join map for + /// The key of the bridge to use for the Markdown output + public void MarkdownJoinMapForDevice(string deviceKey, string bridgeKey) + { + var joinMap = JoinMaps[deviceKey]; + + if (joinMap == null) + { + this.LogInformation("Unable to find joinMap for device with key: '{deviceKey}'", deviceKey); + return; + } + + this.LogInformation("Join map for device '{deviceKey}' on EISC '{eiscKey}':", deviceKey, Key); + joinMap.MarkdownJoinMapInfo(deviceKey, bridgeKey); + } + + /// + /// Used for debugging to trigger an action based on a join number and type + /// + /// The join number to execute the action for + /// The type of join (digital, analog, serial) + /// The state to pass to the action + public void ExecuteJoinAction(uint join, string type, object state) + { + try + { + switch (type.ToLower()) + { + case "digital": { - Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString()); - uo(Convert.ToBoolean(state)); + if (Eisc.BooleanOutput[join].UserObject is Action userObject) + { + this.LogVerbose("Executing Boolean Action"); + userObject(Convert.ToBoolean(state)); + } + else + this.LogVerbose("User Object is null. Nothing to Execute"); + break; } - else - Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); + case "analog": + { + if (Eisc.UShortOutput[join].UserObject is Action userObject) + { + this.LogVerbose("Executing Analog Action"); + userObject(Convert.ToUInt16(state)); + } + else + this.LogVerbose("User Object is null. Nothing to Execute"); + break; + } + case "serial": + { + if (Eisc.StringOutput[join].UserObject is Action userObject) + { + this.LogVerbose("Executing Serial Action"); + userObject(Convert.ToString(state)); + } + else + this.LogVerbose("User Object is null. Nothing to Execute"); + break; + } + default: + { + this.LogVerbose("Unknown join type. Use digital/serial/analog"); + break; + } + } + } + catch (Exception e) + { + this.LogError("ExecuteJoinAction error: {message}", e.Message); + this.LogDebug(e, "Stack Trace: "); + } + + } + + /// + /// Handle incoming sig changes + /// + /// BasicTriList device that triggered the event + /// Event arguments containing the signal information + protected void Eisc_SigChange(object currentDevice, SigEventArgs args) + { + try + { + this.LogVerbose("EiscApiAdvanced change: {type} {number}={value}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + var userObject = args.Sig.UserObject; + + if (userObject == null) return; + + + if (userObject is Action) + { + this.LogDebug("Executing Boolean Action"); + (userObject as Action)(args.Sig.BoolValue); + } + else if (userObject is Action) + { + this.LogDebug("Executing Analog Action"); + (userObject as Action)(args.Sig.UShortValue); + } + else if (userObject is Action) + { + this.LogDebug("Executing Serial Action"); + (userObject as Action)(args.Sig.StringValue); + } + } + catch (Exception e) + { + this.LogError("Eisc_SigChange handler error: {message}", e.Message); + this.LogDebug(e, "Stack Trace: "); + } + } + + #region Implementation of ICommunicationMonitor + + /// + /// Gets or sets the CommunicationMonitor + /// + public StatusMonitorBase CommunicationMonitor { get; private set; } + + #endregion + } + + /// + /// Represents a EiscApiPropertiesConfig + /// + public class EiscApiPropertiesConfig + { + /// + /// Gets or sets the Control + /// + [JsonProperty("control")] + public EssentialsControlPropertiesConfig Control { get; set; } + + /// + /// Gets or sets the Devices + /// + [JsonProperty("devices")] + public List Devices { get; set; } + + /// + /// Gets or sets the Rooms + /// + [JsonProperty("rooms")] + public List Rooms { get; set; } + + + /// + /// Represents a ApiDevicePropertiesConfig + /// + public class ApiDevicePropertiesConfig + { + /// + /// Gets or sets the DeviceKey + /// + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + /// + /// Gets or sets the JoinStart + /// + [JsonProperty("joinStart")] + public uint JoinStart { get; set; } + + /// + /// Gets or sets the JoinMapKey + /// + [JsonProperty("joinMapKey")] + public string JoinMapKey { get; set; } + } + + /// + /// Represents a ApiRoomPropertiesConfig + /// + public class ApiRoomPropertiesConfig + { + /// + /// Gets or sets the RoomKey + /// + [JsonProperty("roomKey")] + public string RoomKey { get; set; } + + /// + /// Gets or sets the JoinStart + /// + [JsonProperty("joinStart")] + public uint JoinStart { get; set; } + + /// + /// Gets or sets the JoinMapKey + /// + [JsonProperty("joinMapKey")] + public string JoinMapKey { get; set; } + } + + } + + /// + /// Factory class for EiscApiAdvanced devices + /// + /// + /// Supported types: + /// eiscapiadv - Create a standard EISC client over TCP/IP + /// eiscapiadvanced - Create a standard EISC client over TCP/IP + /// eiscapiadvancedserver - Create an EISC server + /// eiscapiadvancedclient - Create an EISC client + /// vceiscapiadv - Create a VC-4 EISC client + /// vceiscapiadvanced - Create a VC-4 EISC client + /// eiscapiadvudp - Create a standard EISC client over UDP + /// eiscapiadvancedudp - Create a standard EISC client over UDP + /// + public class EiscApiAdvancedFactory : EssentialsDeviceFactory + { + /// + /// Constructor + /// + public EiscApiAdvancedFactory() + { + TypeNames = new List { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced", "eiscapiadvudp", "eiscapiadvancedudp" }; + } + + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogDebug("Attempting to create new EiscApiAdvanced Device"); + + var controlProperties = CommFactory.GetControlPropertiesConfig(dc); + + BasicTriList eisc; + + switch (dc.Type.ToLower()) + { + case "eiscapiadvudp": + case "eiscapiadvancedudp": + { + eisc = new EthernetIntersystemCommunications(controlProperties.IpIdInt, + controlProperties.TcpSshProperties.Address, Global.ControlSystem); break; } - case "analog": + case "eiscapiadv": + case "eiscapiadvanced": { - var uo = Eisc.BooleanOutput[join].UserObject as Action; - if (uo != null) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString()); - uo(Convert.ToUInt16(state)); - } - else - Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); break; + eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(controlProperties.IpIdInt, + controlProperties.TcpSshProperties.Address, Global.ControlSystem); + break; } - case "serial": + case "eiscapiadvancedserver": { - var uo = Eisc.BooleanOutput[join].UserObject as Action; - if (uo != null) + eisc = new EISCServer(controlProperties.IpIdInt, Global.ControlSystem); + break; + } + case "eiscapiadvancedclient": + { + eisc = new EISCClient(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); + break; + } + case "vceiscapiadv": + case "vceiscapiadvanced": + { + if (string.IsNullOrEmpty(controlProperties.RoomId)) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Executing Action: {0}", uo.ToString()); - uo(Convert.ToString(state)); + Debug.LogInformation("Unable to build VC-4 EISC Client for device {deviceKey}. Room ID is missing or empty", dc.Key); + eisc = null; + break; } - else - Debug.LogMessage(LogEventLevel.Verbose, this, "User Action is null. Nothing to Execute"); + eisc = new VirtualControlEISCClient(controlProperties.IpIdInt, controlProperties.RoomId, + Global.ControlSystem); break; } default: - { - Debug.LogMessage(LogEventLevel.Verbose, "Unknown join type. Use digital/serial/analog"); - break; - } + eisc = null; + break; } - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e); - } - } + if (eisc == null) + { + return null; + } - /// - /// Handles incoming sig changes - /// - /// - /// - protected void Eisc_SigChange(object currentDevice, SigEventArgs args) - { - try - { - Debug.LogMessage(LogEventLevel.Verbose, this, "EiscApiAdvanced change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); - var uo = args.Sig.UserObject; - - if (uo == null) return; - - Debug.LogMessage(LogEventLevel.Debug, this, "Executing Action: {0}", uo.ToString()); - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error in Eisc_SigChange handler: {0}", e); + return new EiscApiAdvanced(dc, eisc); } } - #region Implementation of ICommunicationMonitor - - public StatusMonitorBase CommunicationMonitor { get; private set; } - - #endregion -} - -public class EiscApiPropertiesConfig -{ - [JsonProperty("control")] - public EssentialsControlPropertiesConfig Control { get; set; } - - [JsonProperty("devices")] - public List Devices { get; set; } - - [JsonProperty("rooms")] - public List Rooms { get; set; } - - - public class ApiDevicePropertiesConfig - { - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - [JsonProperty("joinStart")] - public uint JoinStart { get; set; } - - [JsonProperty("joinMapKey")] - public string JoinMapKey { get; set; } - } - - public class ApiRoomPropertiesConfig - { - [JsonProperty("roomKey")] - public string RoomKey { get; set; } - - [JsonProperty("joinStart")] - public uint JoinStart { get; set; } - - [JsonProperty("joinMapKey")] - public string JoinMapKey { get; set; } - } - -} - -public class EiscApiAdvancedFactory : EssentialsDeviceFactory -{ - public EiscApiAdvancedFactory() - { - TypeNames = new List { "eiscapiadv", "eiscapiadvanced", "eiscapiadvancedserver", "eiscapiadvancedclient", "vceiscapiadv", "vceiscapiadvanced" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EiscApiAdvanced Device"); - - var controlProperties = CommFactory.GetControlPropertiesConfig(dc); - - BasicTriList eisc; - - switch (dc.Type.ToLower()) - { - case "eiscapiadv": - case "eiscapiadvanced": - { - eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(controlProperties.IpIdInt, - controlProperties.TcpSshProperties.Address, Global.ControlSystem); - break; - } - case "eiscapiadvancedserver": - { - eisc = new EISCServer(controlProperties.IpIdInt, Global.ControlSystem); - break; - } - case "eiscapiadvancedclient": - { - eisc = new EISCClient(controlProperties.IpIdInt, controlProperties.TcpSshProperties.Address, Global.ControlSystem); - break; - } - case "vceiscapiadv": - case "vceiscapiadvanced": - { - if (string.IsNullOrEmpty(controlProperties.RoomId)) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to build VC-4 EISC Client for device {0}. Room ID is missing or empty", dc.Key); - eisc = null; - break; - } - eisc = new VirtualControlEISCClient(controlProperties.IpIdInt, controlProperties.RoomId, - Global.ControlSystem); - break; - } - default: - eisc = null; - break; - } - - if (eisc == null) - { - return null; - } - - return new EiscApiAdvanced(dc, eisc); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs b/src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs index 3a3ceff2..8d183b77 100644 --- a/src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs +++ b/src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs @@ -3,62 +3,71 @@ using Serilog.Events; //using PepperDash.Essentials.Devices.Common.Cameras; -namespace PepperDash.Essentials.Core.Bridges; - -/// -/// Helper methods for bridges -/// -public static class BridgeHelper +namespace PepperDash.Essentials.Core.Bridges { - public static void PrintJoinMap(string command) + /// + /// Helper methods for bridges + /// + public static class BridgeHelper { - var targets = command.Split(' '); - - var bridgeKey = targets[0].Trim(); - - if (!(DeviceManager.GetDeviceForKey(bridgeKey) is EiscApiAdvanced bridge)) + /// + /// PrintJoinMp method + /// + /// target bridgekey to print join map for + public static void PrintJoinMap(string command) { - Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey); - return; + var targets = command.Split(' '); + + var bridgeKey = targets[0].Trim(); + + if (!(DeviceManager.GetDeviceForKey(bridgeKey) is EiscApiAdvanced bridge)) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey); + return; + } + + if (targets.Length > 1) + { + var deviceKey = targets[1].Trim(); + + if (string.IsNullOrEmpty(deviceKey)) return; + bridge.PrintJoinMapForDevice(deviceKey); + } + else + { + bridge.PrintJoinMaps(); + } } - - if (targets.Length > 1) + /// + /// JoinmapMarkdown method + /// + public static void JoinmapMarkdown(string command) { - var deviceKey = targets[1].Trim(); + var targets = command.Split(' '); - if (string.IsNullOrEmpty(deviceKey)) return; - bridge.PrintJoinMapForDevice(deviceKey); - } - else - { - bridge.PrintJoinMaps(); + var bridgeKey = targets[0].Trim(); + + var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced; + + if (bridge == null) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey); + return; + } + + if (targets.Length > 1) + { + var deviceKey = targets[1].Trim(); + + if (string.IsNullOrEmpty(deviceKey)) return; + bridge.MarkdownJoinMapForDevice(deviceKey, bridgeKey); + } + else + { + bridge.MarkdownForBridge(bridgeKey); + + } } } - public static void JoinmapMarkdown(string command) - { - var targets = command.Split(' '); - var bridgeKey = targets[0].Trim(); - - var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced; - - if (bridge == null) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey); - return; - } - - if (targets.Length > 1) - { - var deviceKey = targets[1].Trim(); - - if (string.IsNullOrEmpty(deviceKey)) return; - bridge.MarkdownJoinMapForDevice(deviceKey, bridgeKey); - } - else - { - bridge.MarkdownForBridge(bridgeKey); - - } - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/IBridge.cs b/src/PepperDash.Essentials.Core/Bridges/IBridge.cs index 87b4bd35..34c11171 100644 --- a/src/PepperDash.Essentials.Core/Bridges/IBridge.cs +++ b/src/PepperDash.Essentials.Core/Bridges/IBridge.cs @@ -1,11 +1,19 @@ using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core.Bridges; - -/// -/// Defines a device that uses JoinMapBaseAdvanced for its join map -/// -public interface IBridgeAdvanced +namespace PepperDash.Essentials.Core.Bridges { - void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); + /// + /// Defines the contract for IBridgeAdvanced + /// + public interface IBridgeAdvanced + { + /// + /// Links the bridge to the API using the provided trilist, join start, join map key, and bridge. + /// + /// The trilist to link to. + /// The starting join number. + /// The key for the join map. + /// The EISC API bridge. + void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AirMediaControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AirMediaControllerJoinMap.cs index 33fca20e..2bd218f6 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AirMediaControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AirMediaControllerJoinMap.cs @@ -1,70 +1,110 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class AirMediaControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IsInSession")] - public JoinDataComplete IsInSession = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media In Sharing Session", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HdmiVideoSync")] - public JoinDataComplete HdmiVideoSync = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Has HDMI Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("AutomaticInputRoutingEnabled")] - public JoinDataComplete AutomaticInputRoutingEnabled = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Automatic Input Routing Enable(d)", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoOut")] - public JoinDataComplete VideoOut = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Video Route Select / Feedback", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("ErrorFB")] - public JoinDataComplete ErrorFB = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Error Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("NumberOfUsersConnectedFB")] - public JoinDataComplete NumberOfUsersConnectedFB = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Number of Users Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("LoginCode")] - public JoinDataComplete LoginCode = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Login Code Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Device Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("ConnectionAddressFB")] - public JoinDataComplete ConnectionAddressFB = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media IP Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("HostnameFB")] - public JoinDataComplete HostnameFB = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Hostname", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("SerialNumberFeedback")] - public JoinDataComplete SerialNumberFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Air Media Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a AirMediaControllerJoinMap /// - /// Join this join map will start at - public AirMediaControllerJoinMap(uint joinStart) - : this(joinStart, typeof(AirMediaControllerJoinMap)) + public class AirMediaControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Air Media Online status + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected AirMediaControllerJoinMap(uint joinStart, Type type) : base(joinStart, type){} + /// + /// Air Media In Sharing Session status + /// + [JoinName("IsInSession")] + public JoinDataComplete IsInSession = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media In Sharing Session", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Air Media Has HDMI Video Sync status + /// + [JoinName("HdmiVideoSync")] + public JoinDataComplete HdmiVideoSync = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Has HDMI Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Air Media Automatic Input Routing Enable(d) + /// + [JoinName("AutomaticInputRoutingEnabled")] + public JoinDataComplete AutomaticInputRoutingEnabled = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Automatic Input Routing Enable(d)", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Air Media Video Route Select / Feedback + /// + [JoinName("VideoOut")] + public JoinDataComplete VideoOut = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Video Route Select / Feedback", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Air Media Error Status Feedback + /// + [JoinName("ErrorFB")] + public JoinDataComplete ErrorFB = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Error Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Air Media Number of Users Connected Feedback + /// + [JoinName("NumberOfUsersConnectedFB")] + public JoinDataComplete NumberOfUsersConnectedFB = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Number of Users Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Air Media Login Code Set / Get + /// + [JoinName("LoginCode")] + public JoinDataComplete LoginCode = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Login Code Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Air Media Device Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Device Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Air Media IP Address Feedback + /// + [JoinName("ConnectionAddressFB")] + public JoinDataComplete ConnectionAddressFB = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media IP Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Air Media Hostname Feedback + /// + [JoinName("HostnameFB")] + public JoinDataComplete HostnameFB = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Hostname", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Air Media Serial Number Feedback + /// + [JoinName("SerialNumberFeedback")] + public JoinDataComplete SerialNumberFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Air Media Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public AirMediaControllerJoinMap(uint joinStart) + : this(joinStart, typeof(AirMediaControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected AirMediaControllerJoinMap(uint joinStart, Type type) : base(joinStart, type){} + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AppleTvJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AppleTvJoinMap.cs index 3b3eaa06..c337f9cf 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AppleTvJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/AppleTvJoinMap.cs @@ -1,52 +1,77 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class AppleTvJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("UpArrow")] - public JoinDataComplete UpArrow = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DnArrow")] - public JoinDataComplete DnArrow = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("LeftArrow")] - public JoinDataComplete LeftArrow = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RightArrow")] - public JoinDataComplete RightArrow = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Menu")] - public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Select")] - public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PlayPause")] - public JoinDataComplete PlayPause = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "AppleTv Play/Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a AppleTvJoinMap /// - /// Join this join map will start at - public AppleTvJoinMap(uint joinStart) - : base(joinStart, typeof(AppleTvJoinMap)) + public class AppleTvJoinMap : JoinMapBaseAdvanced { - } + /// + /// AppleTv Nav Up + /// + [JoinName("UpArrow")] + public JoinDataComplete UpArrow = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - public AppleTvJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// AppleTv Nav Down + /// + [JoinName("DnArrow")] + public JoinDataComplete DnArrow = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// AppleTv Nav Left + /// + [JoinName("LeftArrow")] + public JoinDataComplete LeftArrow = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// AppleTv Nav Right + /// + [JoinName("RightArrow")] + public JoinDataComplete RightArrow = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// AppleTv Menu + /// + [JoinName("Menu")] + public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// AppleTv Select + /// + [JoinName("Select")] + public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// AppleTv Play/Pause + /// + [JoinName("PlayPause")] + public JoinDataComplete PlayPause = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "AppleTv Play/Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public AppleTvJoinMap(uint joinStart) + : base(joinStart, typeof(AppleTvJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + public AppleTvJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/C2nRthsControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/C2nRthsControllerJoinMap.cs index 17f8f4db..bb9da22f 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/C2nRthsControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/C2nRthsControllerJoinMap.cs @@ -1,44 +1,63 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class C2nRthsControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Temp Sensor Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("TemperatureFormat")] - public JoinDataComplete TemperatureFormat = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Temp Sensor Unit Format", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Temperature")] - public JoinDataComplete Temperature = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Temp Sensor Temperature Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Humidity")] - public JoinDataComplete Humidity = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Temp Sensor Humidity Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Temp Sensor Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a C2nRthsControllerJoinMap /// - /// Join this join map will start at - public C2nRthsControllerJoinMap(uint joinStart) - : this(joinStart, typeof(C2nRthsControllerJoinMap)) + public class C2nRthsControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// C2nRthsController Online status + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Temp Sensor Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected C2nRthsControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// Temperature Format (C/F) + /// + [JoinName("TemperatureFormat")] + public JoinDataComplete TemperatureFormat = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Temp Sensor Unit Format", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Temperature Sensor Feedbacks + /// + [JoinName("Temperature")] + public JoinDataComplete Temperature = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Temp Sensor Temperature Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Humidity Sensor Feedbacks + /// + [JoinName("Humidity")] + public JoinDataComplete Humidity = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Temp Sensor Humidity Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Temp Sensor Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Temp Sensor Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public C2nRthsControllerJoinMap(uint joinStart) + : this(joinStart, typeof(C2nRthsControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected C2nRthsControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CameraControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CameraControllerJoinMap.cs index 0cb011fa..aad43513 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CameraControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CameraControllerJoinMap.cs @@ -1,68 +1,140 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -/// -/// Join map for CameraBase devices -/// -public class CameraControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("TiltUp")] - public JoinDataComplete TiltUp = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, new JoinMetadata { Description = "Tilt Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("TiltDown")] - public JoinDataComplete TiltDown = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, new JoinMetadata { Description = "Tilt Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("PanLeft")] - public JoinDataComplete PanLeft = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, new JoinMetadata { Description = "Pan Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("PanRight")] - public JoinDataComplete PanRight = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, new JoinMetadata { Description = "Pan Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ZoomIn")] - public JoinDataComplete ZoomIn = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, new JoinMetadata { Description = "Zoom In", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ZoomOut")] - public JoinDataComplete ZoomOut = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, new JoinMetadata { Description = "Zoom Out", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, new JoinMetadata { Description = "Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - [JoinName("PowerOn")] - public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("PowerOff")] - public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("NumberOfPresets")] - public JoinDataComplete NumberOfPresets = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, new JoinMetadata { Description = "Tells Essentials the number of defined presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - [JoinName("PresetRecallStart")] - public JoinDataComplete PresetRecallStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Recall Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("PresetLabelStart")] - public JoinDataComplete PresetLabelStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Label Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); - [JoinName("PresetSaveStart")] - public JoinDataComplete PresetSaveStart = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Save Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("CameraModeAuto")] - public JoinDataComplete CameraModeAuto = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Auto", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("CameraModeManual")] - public JoinDataComplete CameraModeManual = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Manual", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("CameraModeOff")] - public JoinDataComplete CameraModeOff = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SupportsCameraModeAuto")] - public JoinDataComplete SupportsCameraModeAuto = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Camera Mode Auto", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("SupportsCameraModeOff")] - public JoinDataComplete SupportsCameraModeOff = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Camera Mode Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("SupportsPresets")] - public JoinDataComplete SupportsPresets = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a CameraControllerJoinMap /// - /// Join this join map will start at - public CameraControllerJoinMap(uint joinStart) - : this(joinStart, typeof(CameraControllerJoinMap)) + public class CameraControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Tilt Up + /// + [JoinName("TiltUp")] + public JoinDataComplete TiltUp = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, new JoinMetadata { Description = "Tilt Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Tilt Down + /// + [JoinName("TiltDown")] + public JoinDataComplete TiltDown = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, new JoinMetadata { Description = "Tilt Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Pan Left + /// + [JoinName("PanLeft")] + public JoinDataComplete PanLeft = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, new JoinMetadata { Description = "Pan Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Pan Right + /// + [JoinName("PanRight")] + public JoinDataComplete PanRight = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, new JoinMetadata { Description = "Pan Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Zoom In + /// + [JoinName("ZoomIn")] + public JoinDataComplete ZoomIn = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, new JoinMetadata { Description = "Zoom In", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Zoom Out + /// + [JoinName("ZoomOut")] + public JoinDataComplete ZoomOut = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, new JoinMetadata { Description = "Zoom Out", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Is Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, new JoinMetadata { Description = "Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Power On + /// + [JoinName("PowerOn")] + public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Power Off + /// + [JoinName("PowerOff")] + public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Number Of Presets + /// + [JoinName("NumberOfPresets")] + public JoinDataComplete NumberOfPresets = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, new JoinMetadata { Description = "Tells Essentials the number of defined presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Preset Recall Start + /// + [JoinName("PresetRecallStart")] + public JoinDataComplete PresetRecallStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Recall Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Preset Label Start + /// + [JoinName("PresetLabelStart")] + public JoinDataComplete PresetLabelStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Label Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Preset Save Start + /// + [JoinName("PresetSaveStart")] + public JoinDataComplete PresetSaveStart = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 20 }, new JoinMetadata { Description = "Preset Save Start", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Camera Mode Auto + /// + [JoinName("CameraModeAuto")] + public JoinDataComplete CameraModeAuto = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Auto", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Camera Mode Manual + /// + [JoinName("CameraModeManual")] + public JoinDataComplete CameraModeManual = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Manual", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Camera Mode Off + /// + [JoinName("CameraModeOff")] + public JoinDataComplete CameraModeOff = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1 }, new JoinMetadata { Description = "Camera Mode Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Supports Camera Mode Manual + /// + [JoinName("SupportsCameraModeAuto")] + public JoinDataComplete SupportsCameraModeAuto = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Camera Mode Auto", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Supports Camera Mode Off + /// + [JoinName("SupportsCameraModeOff")] + public JoinDataComplete SupportsCameraModeOff = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Camera Mode Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Supports Presets + /// + [JoinName("SupportsPresets")] + public JoinDataComplete SupportsPresets = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1 }, new JoinMetadata { Description = "Supports Presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected CameraControllerJoinMap(uint joinStart, Type type) : base(joinStart, type){} + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public CameraControllerJoinMap(uint joinStart) + : this(joinStart, typeof(CameraControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected CameraControllerJoinMap(uint joinStart, Type type) : base(joinStart, type){} + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CenOdtOccupancySensorBaseJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CenOdtOccupancySensorBaseJoinMap.cs index b585916b..b52c5309 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CenOdtOccupancySensorBaseJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/CenOdtOccupancySensorBaseJoinMap.cs @@ -1,196 +1,320 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class CenOdtOccupancySensorBaseJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - #region Digitals - - [JoinName("Online")] - public JoinDataComplete Online = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ForceOccupied")] - public JoinDataComplete ForceOccupied = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Force Occupied", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ForceVacant")] - public JoinDataComplete ForceVacant = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Force Vacant", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableRawStates")] - public JoinDataComplete EnableRawStates = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Raw States", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RoomOccupiedFeedback")] - public JoinDataComplete RoomOccupiedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Room Occupied Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("GraceOccupancyDetectedFeedback")] - public JoinDataComplete GraceOccupancyDetectedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Grace Occupancy Detected Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RoomVacantFeedback")] - public JoinDataComplete RoomVacantFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Room Vacant Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyFeedback")] - public JoinDataComplete RawOccupancyFeedback = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Raw Occupancy Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyPirFeedback")] - public JoinDataComplete RawOccupancyPirFeedback = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Raw Occupancy Pir Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyUsFeedback")] - public JoinDataComplete RawOccupancyUsFeedback = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Raw Occupancy Us Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IdentityModeOn")] - public JoinDataComplete IdentityMode = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Identity Mode", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IdentityModeFeedback")] - public JoinDataComplete IdentityModeFeedback = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Identity Mode Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableLedFlash")] - public JoinDataComplete EnableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Led Flash", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableLedFlash")] - public JoinDataComplete DisableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Disable Led Flash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableShortTimeout")] - public JoinDataComplete EnableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Short Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableShortTimeout")] - public JoinDataComplete DisableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Disable Short Timeout", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OrWhenVacated")] - public JoinDataComplete OrWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Or When Vacated", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("AndWhenVacated")] - public JoinDataComplete AndWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, - new JoinMetadata { Description = "AndWhenVacated", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableUsA")] - public JoinDataComplete EnableUsA = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Us A", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableUsA")] - public JoinDataComplete DisableUsA = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, - new JoinMetadata { Description = "Disable Us A", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableUsB")] - public JoinDataComplete EnableUsB = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Us B", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableUsB")] - public JoinDataComplete DisableUsB = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, - new JoinMetadata { Description = "Disable Us B", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnablePir")] - public JoinDataComplete EnablePir = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Pir", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisablePir")] - public JoinDataComplete DisablePir = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "Disable Pir", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementUsInOccupiedState")] - public JoinDataComplete IncrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "Increment Us In OccupiedState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementUsInOccupiedState")] - public JoinDataComplete DecrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "Dencrement Us In Occupied State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementUsInVacantState")] - public JoinDataComplete IncrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "Increment Us In VacantState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementUsInVacantState")] - public JoinDataComplete DecrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, - new JoinMetadata { Description = "Decrement Us In VacantState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementPirInOccupiedState")] - public JoinDataComplete IncrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, - new JoinMetadata { Description = "Increment Pir In Occupied State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementPirInOccupiedState")] - public JoinDataComplete DecrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, - new JoinMetadata { Description = "Decrement Pir In OccupiedState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementPirInVacantState")] - public JoinDataComplete IncrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, - new JoinMetadata { Description = "Increment Pir In Vacant State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementPirInVacantState")] - public JoinDataComplete DecrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "Decrement Pir In Vacant State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - #endregion - - #region Analog - - [JoinName("Timeout")] - public JoinDataComplete Timeout = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("TimeoutLocalFeedback")] - public JoinDataComplete TimeoutLocalFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Timeout Local Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InternalPhotoSensorValue")] - public JoinDataComplete InternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Internal PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("UsSensitivityInOccupiedState")] - public JoinDataComplete UsSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Us Sensitivity In Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("UsSensitivityInVacantState")] - public JoinDataComplete UsSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Us Sensitivity In Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("PirSensitivityInOccupiedState")] - public JoinDataComplete PirSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Pir Sensitivity In Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("PirSensitivityInVacantState")] - public JoinDataComplete PirSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Pir Sensitivity In Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - #endregion - - #region Serial - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - #endregion - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a CenOdtOccupancySensorBaseJoinMap /// - /// Join this join map will start at - public CenOdtOccupancySensorBaseJoinMap(uint joinStart) - : this(joinStart, typeof(CenOdtOccupancySensorBaseJoinMap)) + public class CenOdtOccupancySensorBaseJoinMap : JoinMapBaseAdvanced { + #region Digitals + + /// + /// Online + /// + [JoinName("Online")] + public JoinDataComplete Online = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Force Occupied + /// + [JoinName("ForceOccupied")] + public JoinDataComplete ForceOccupied = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Force Occupied", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Force Vacant + /// + [JoinName("ForceVacant")] + public JoinDataComplete ForceVacant = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Force Vacant", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Raw States + /// + [JoinName("EnableRawStates")] + public JoinDataComplete EnableRawStates = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Raw States", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Raw States + /// + [JoinName("RoomOccupiedFeedback")] + public JoinDataComplete RoomOccupiedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Room Occupied Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Grace Occupancy Detected Feedback + /// + [JoinName("GraceOccupancyDetectedFeedback")] + public JoinDataComplete GraceOccupancyDetectedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Grace Occupancy Detected Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Room Vacant Feedback + /// + [JoinName("RoomVacantFeedback")] + public JoinDataComplete RoomVacantFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Room Vacant Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Raw Occupancy Feedback + /// + [JoinName("RawOccupancyFeedback")] + public JoinDataComplete RawOccupancyFeedback = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Raw Occupancy Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Raw Occupancy Pir Feedback + /// + [JoinName("RawOccupancyPirFeedback")] + public JoinDataComplete RawOccupancyPirFeedback = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Raw Occupancy Pir Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Raw Occupancy Us Feedback + /// + [JoinName("RawOccupancyUsFeedback")] + public JoinDataComplete RawOccupancyUsFeedback = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Raw Occupancy Us Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Identity Mode On + /// + [JoinName("IdentityModeOn")] + public JoinDataComplete IdentityMode = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Identity Mode", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Identity Mode Off + /// + [JoinName("IdentityModeFeedback")] + public JoinDataComplete IdentityModeFeedback = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Identity Mode Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Led Flash + /// + [JoinName("EnableLedFlash")] + public JoinDataComplete EnableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Led Flash", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Led Flash + /// + [JoinName("DisableLedFlash")] + public JoinDataComplete DisableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Disable Led Flash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Short Timeout + /// + [JoinName("EnableShortTimeout")] + public JoinDataComplete EnableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Short Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Short Timeout + /// + [JoinName("DisableShortTimeout")] + public JoinDataComplete DisableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Disable Short Timeout", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Or When Vacated + /// + [JoinName("OrWhenVacated")] + public JoinDataComplete OrWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Or When Vacated", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// And When Vacated + /// + [JoinName("AndWhenVacated")] + public JoinDataComplete AndWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, + new JoinMetadata { Description = "AndWhenVacated", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Us A + /// + [JoinName("EnableUsA")] + public JoinDataComplete EnableUsA = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Us A", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Us A + /// + [JoinName("DisableUsA")] + public JoinDataComplete DisableUsA = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, + new JoinMetadata { Description = "Disable Us A", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Us B + /// + [JoinName("EnableUsB")] + public JoinDataComplete EnableUsB = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Us B", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Us B + /// + [JoinName("DisableUsB")] + public JoinDataComplete DisableUsB = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, + new JoinMetadata { Description = "Disable Us B", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enable Pir + /// + [JoinName("EnablePir")] + public JoinDataComplete EnablePir = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Pir", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disable Pir + /// + [JoinName("DisablePir")] + public JoinDataComplete DisablePir = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "Disable Pir", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Increment Us In Occupied State + /// + [JoinName("IncrementUsInOccupiedState")] + public JoinDataComplete IncrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "Increment Us In OccupiedState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Decrement Us In Occupied State + /// + [JoinName("DecrementUsInOccupiedState")] + public JoinDataComplete DecrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "Dencrement Us In Occupied State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Increment Us In Vacant State + /// + [JoinName("IncrementUsInVacantState")] + public JoinDataComplete IncrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "Increment Us In VacantState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Decrement Us In Vacant State + /// + [JoinName("DecrementUsInVacantState")] + public JoinDataComplete DecrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, + new JoinMetadata { Description = "Decrement Us In VacantState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Increment Pir In Occupied State + /// + [JoinName("IncrementPirInOccupiedState")] + public JoinDataComplete IncrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, + new JoinMetadata { Description = "Increment Pir In Occupied State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Decrement Pir In Occupied State + /// + [JoinName("DecrementPirInOccupiedState")] + public JoinDataComplete DecrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, + new JoinMetadata { Description = "Decrement Pir In OccupiedState", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Increment Pir In Vacant State + /// + [JoinName("IncrementPirInVacantState")] + public JoinDataComplete IncrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, + new JoinMetadata { Description = "Increment Pir In Vacant State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Decrement Pir In Vacant State + /// + [JoinName("DecrementPirInVacantState")] + public JoinDataComplete DecrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "Decrement Pir In Vacant State", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + #endregion + + #region Analog + /// + /// Timeout + /// + [JoinName("Timeout")] + public JoinDataComplete Timeout = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Timeout Local Feedback + /// + [JoinName("TimeoutLocalFeedback")] + public JoinDataComplete TimeoutLocalFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Timeout Local Feedback", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Internal PhotoSensor Value + /// + [JoinName("InternalPhotoSensorValue")] + public JoinDataComplete InternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Internal PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// External PhotoSensor Value + /// + [JoinName("UsSensitivityInOccupiedState")] + public JoinDataComplete UsSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Us Sensitivity In Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Us Sensitivity In Vacant State + /// + [JoinName("UsSensitivityInVacantState")] + public JoinDataComplete UsSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Us Sensitivity In Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Pir Sensitivity In Occupied State + /// + [JoinName("PirSensitivityInOccupiedState")] + public JoinDataComplete PirSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Pir Sensitivity In Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Pir Sensitivity In Vacant State + /// + [JoinName("PirSensitivityInVacantState")] + public JoinDataComplete PirSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Pir Sensitivity In Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + #endregion + + #region Serial + + /// + /// Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + #endregion + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public CenOdtOccupancySensorBaseJoinMap(uint joinStart) + : this(joinStart, typeof(CenOdtOccupancySensorBaseJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected CenOdtOccupancySensorBaseJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected CenOdtOccupancySensorBaseJoinMap(uint joinStart, Type type) : base(joinStart, type) - { - } } diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DisplayControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DisplayControllerJoinMap.cs index 2ab95b8e..3741f951 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DisplayControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DisplayControllerJoinMap.cs @@ -1,84 +1,133 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DisplayControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("PowerOff")] - public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PowerOn")] - public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IsTwoWayDisplay")] - public JoinDataComplete IsTwoWayDisplay = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Is Two Way Display", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeUp")] - public JoinDataComplete VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeLevel")] - public JoinDataComplete VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Level", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("VolumeDown")] - public JoinDataComplete VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeMute")] - public JoinDataComplete VolumeMute = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Mute", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeMuteOn")] - public JoinDataComplete VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Mute On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeMuteOff")] - public JoinDataComplete VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "Volume Mute Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputSelectOffset")] - public JoinDataComplete InputSelectOffset = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, - new JoinMetadata { Description = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputNamesOffset")] - public JoinDataComplete InputNamesOffset = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, - new JoinMetadata { Description = "Input Names Offset", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputSelect")] - public JoinDataComplete InputSelect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("ButtonVisibilityOffset")] - public JoinDataComplete ButtonVisibilityOffset = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 10 }, - new JoinMetadata { Description = "Button Visibility Offset", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalSerial }); - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, - new JoinMetadata { Description = "Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DisplayControllerJoinMap /// - /// Join this join map will start at - public DisplayControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DisplayControllerJoinMap)) + public class DisplayControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Power Off + /// + [JoinName("PowerOff")] + public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Power On + /// + [JoinName("PowerOn")] + public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Is Two Way Display + /// + [JoinName("IsTwoWayDisplay")] + public JoinDataComplete IsTwoWayDisplay = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Is Two Way Display", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Volume Up + /// + [JoinName("VolumeUp")] + public JoinDataComplete VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Volume Level + /// + [JoinName("VolumeLevel")] + public JoinDataComplete VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Level", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Volume Down + /// + [JoinName("VolumeDown")] + public JoinDataComplete VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Volume Mute + /// + [JoinName("VolumeMute")] + public JoinDataComplete VolumeMute = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Mute", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Volume Mute On + /// + [JoinName("VolumeMuteOn")] + public JoinDataComplete VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Mute On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Volume Mute Off + /// + [JoinName("VolumeMuteOff")] + public JoinDataComplete VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "Volume Mute Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input Select Offset + /// + [JoinName("InputSelectOffset")] + public JoinDataComplete InputSelectOffset = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, + new JoinMetadata { Description = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input Names Offset + /// + [JoinName("InputNamesOffset")] + public JoinDataComplete InputNamesOffset = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, + new JoinMetadata { Description = "Input Names Offset", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Input Select + /// + [JoinName("InputSelect")] + public JoinDataComplete InputSelect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Button Visibility Offset + /// + [JoinName("ButtonVisibilityOffset")] + public JoinDataComplete ButtonVisibilityOffset = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 10 }, + new JoinMetadata { Description = "Button Visibility Offset", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalSerial }); + + /// + /// Is Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, + new JoinMetadata { Description = "Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DisplayControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DisplayControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DisplayControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DisplayControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmBladeChassisControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmBladeChassisControllerJoinMap.cs index 3edd1222..c6488bba 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmBladeChassisControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmBladeChassisControllerJoinMap.cs @@ -1,73 +1,113 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; -public class DmBladeChassisControllerJoinMap : JoinMapBaseAdvanced { - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "DM Blade Chassis Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoSyncStatus")] - public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputEndpointOnline")] - public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputEndpointOnline")] - public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("TxAdvancedIsPresent")] - public JoinDataComplete TxAdvancedIsPresent = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Tx Advanced Is Present", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputVideo")] - public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Output Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportState")] - public JoinDataComplete HdcpSupportState = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Input HDCP Support State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportCapability")] - public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Input HDCP Support Capability", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InputNames")] - public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputNames")] - public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputCurrentVideoInputNames")] - public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputCurrentResolution")] - public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 128 }, - new JoinMetadata { Description = "DM Blade Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - +namespace PepperDash.Essentials.Core.Bridges { /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmBladeChassisControllerJoinMap /// - /// Join this join map will start at - public DmBladeChassisControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmBladeChassisControllerJoinMap)) - { - } + public class DmBladeChassisControllerJoinMap : JoinMapBaseAdvanced { - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmBladeChassisControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { - } + /// + /// DM Blade Chassis Online status + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Blade Chassis Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Blade Input Video Sync + /// + [JoinName("VideoSyncStatus")] + public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Blade Chassis Input Endpoint Online + /// + [JoinName("InputEndpointOnline")] + public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Blade Chassis Output Endpoint Online + /// + [JoinName("OutputEndpointOnline")] + public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Blade Chassis Tx Advanced Is Present + /// + [JoinName("TxAdvancedIsPresent")] + public JoinDataComplete TxAdvancedIsPresent = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Tx Advanced Is Present", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Blade Chassis Rx Advanced Is Present + /// + [JoinName("OutputVideo")] + public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Output Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Blade Chassis Input HDCP Support State + /// + [JoinName("HdcpSupportState")] + public JoinDataComplete HdcpSupportState = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Input HDCP Support State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Blade Chassis Input HDCP Support Capability + /// + [JoinName("HdcpSupportCapability")] + public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Input HDCP Support Capability", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Blade Chassis Input Names + /// + [JoinName("InputNames")] + public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Blade Chassis Output Names + /// + [JoinName("OutputNames")] + public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Blade Chassis Video Output Currently Routed Video Input Name + /// + [JoinName("OutputCurrentVideoInputNames")] + public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Blade Chassis Input Current Resolution + /// + [JoinName("InputCurrentResolution")] + public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 128 }, + new JoinMetadata { Description = "DM Blade Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmBladeChassisControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmBladeChassisControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmBladeChassisControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } + + } } diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmChassisControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmChassisControllerJoinMap.cs index db3701b3..9d2217cc 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmChassisControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmChassisControllerJoinMap.cs @@ -1,169 +1,259 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmChassisControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("EnableAudioBreakaway")] - public JoinDataComplete EnableAudioBreakaway = new JoinDataComplete( - new JoinData {JoinNumber = 4, JoinSpan = 1}, - new JoinMetadata - { - Description = "DM Chassis enable audio breakaway routing", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("EnableUsbBreakaway")] - public JoinDataComplete EnableUsbBreakaway = new JoinDataComplete( - new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata - { - Description = "DM Chassis enable USB breakaway routing", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM Chassis Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("SystemId")] - public JoinDataComplete SystemId = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, - new JoinMetadata { Description = "DM Chassis SystemId Get/Set/Trigger/", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalAnalog }); - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "DM Chassis Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoSyncStatus")] - public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputEndpointOnline")] - public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputEndpointOnline")] - public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("TxAdvancedIsPresent")] - public JoinDataComplete TxAdvancedIsPresent = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Tx Advanced Is Present", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputDisabledByHdcp")] - public JoinDataComplete OutputDisabledByHdcp = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Disabled by HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputVideo")] - public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Video Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutputAudio")] - public JoinDataComplete OutputAudio = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Audio Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutputUsb")] - public JoinDataComplete OutputUsb = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output USB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InputUsb")] - public JoinDataComplete InputUsb = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Usb Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportState")] - public JoinDataComplete HdcpSupportState = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input HDCP Support State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportCapability")] - public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input HDCP Support Capability", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InputStreamCardState")] - public JoinDataComplete InputStreamCardState = new JoinDataComplete(new JoinData { JoinNumber = 1501, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Stream Input Start (1), Stop (2), Pause (3) with Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutputStreamCardState")] - public JoinDataComplete OutputStreamCardState = new JoinDataComplete(new JoinData { JoinNumber = 1601, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Stream Output Start (1), Stop (2), Pause (3) with Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("NoRouteName")] - public JoinDataComplete NoRouteName = new JoinDataComplete(new JoinData { JoinNumber = 100, JoinSpan = 1 }, - new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputNames")] - public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputNames")] - public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputVideoNames")] public JoinDataComplete InputVideoNames = - new JoinDataComplete(new JoinData {JoinNumber = 501, JoinSpan = 200}, + /// + /// Represents a DmChassisControllerJoinMap + /// + public class DmChassisControllerJoinMap : JoinMapBaseAdvanced + { + /// + /// DM Chassis enable audio breakaway routing + /// + [JoinName("EnableAudioBreakaway")] + public JoinDataComplete EnableAudioBreakaway = new JoinDataComplete( + new JoinData {JoinNumber = 4, JoinSpan = 1}, new JoinMetadata { - Description = "DM Chassis Video Input Names", + Description = "DM Chassis enable audio breakaway routing", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// DM Chassis enable USB breakaway routing + /// + [JoinName("EnableUsbBreakaway")] + public JoinDataComplete EnableUsbBreakaway = new JoinDataComplete( + new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata + { + Description = "DM Chassis enable USB breakaway routing", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// DM Chassis Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Chassis Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis SystemId Get/Set/Trigger + /// + [JoinName("SystemId")] + public JoinDataComplete SystemId = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Chassis SystemId Get/Set/Trigger/", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalAnalog }); + + /// + /// DM Chassis Online status + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Chassis Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Input Video Sync + /// + [JoinName("VideoSyncStatus")] + public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Input Endpoint Online + /// + [JoinName("InputEndpointOnline")] + public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Output Endpoint Online + /// + [JoinName("OutputEndpointOnline")] + public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Tx Advanced Is Present + /// + [JoinName("TxAdvancedIsPresent")] + public JoinDataComplete TxAdvancedIsPresent = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Tx Advanced Is Present", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Rx Advanced Is Present + /// + [JoinName("OutputDisabledByHdcp")] + public JoinDataComplete OutputDisabledByHdcp = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Disabled by HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Output Set / Get + /// + [JoinName("OutputVideo")] + public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Video Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Output Audio Set / Get + /// + [JoinName("OutputAudio")] + public JoinDataComplete OutputAudio = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Audio Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input Set / Get + /// + [JoinName("OutputUsb")] + public JoinDataComplete OutputUsb = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output USB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input Set / Get + /// + [JoinName("InputUsb")] + public JoinDataComplete InputUsb = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Usb Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input HDCP Support State + /// + [JoinName("HdcpSupportState")] + public JoinDataComplete HdcpSupportState = new JoinDataComplete(new JoinData { JoinNumber = 1001, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input HDCP Support State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input HDCP Support Capability + /// + [JoinName("HdcpSupportCapability")] + public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 1201, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input HDCP Support Capability", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Stream Input Start (1), Stop (2), Pause (3) with Feedback + /// + [JoinName("InputStreamCardState")] + public JoinDataComplete InputStreamCardState = new JoinDataComplete(new JoinData { JoinNumber = 1501, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Stream Input Start (1), Stop (2), Pause (3) with Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Stream Output Start (1), Stop (2), Pause (3) with Feedback + /// + [JoinName("OutputStreamCardState")] + public JoinDataComplete OutputStreamCardState = new JoinDataComplete(new JoinData { JoinNumber = 1601, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Stream Output Start (1), Stop (2), Pause (3) with Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis No Route Name + /// + [JoinName("NoRouteName")] + public JoinDataComplete NoRouteName = new JoinDataComplete(new JoinData { JoinNumber = 100, JoinSpan = 1 }, + new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Input Names + /// + [JoinName("InputNames")] + public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Output Names + /// + [JoinName("OutputNames")] + public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Video Input Names + /// + [JoinName("InputVideoNames")] public JoinDataComplete InputVideoNames = + new JoinDataComplete(new JoinData {JoinNumber = 501, JoinSpan = 200}, + new JoinMetadata + { + Description = "DM Chassis Video Input Names", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Audio Input Names + /// + [JoinName("InputAudioNames")] + public JoinDataComplete InputAudioNames = + new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 200 }, + new JoinMetadata + { + Description = "DM Chassis Audio Input Names", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Video Output Names + /// + [JoinName("OutputVideoNames")] + public JoinDataComplete OutputVideoNames = + new JoinDataComplete(new JoinData { JoinNumber = 901, JoinSpan = 200 }, + new JoinMetadata + { + Description = "DM Chassis Video Output Names", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Audio Output Names + /// + [JoinName("OutputAudioNames")] + public JoinDataComplete OutputAudioNames = + new JoinDataComplete(new JoinData { JoinNumber = 1101, JoinSpan = 200 }, + new JoinMetadata + { + Description = "DM Chassis Audio Output Names", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Video Output Currently Routed Video Input Name + /// + [JoinName("OutputCurrentVideoInputNames")] + public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Audio Output Currently Routed Audio Input Name + /// + [JoinName("OutputCurrentAudioInputNames")] + public JoinDataComplete OutputCurrentAudioInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2201, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Audio Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Input Current Resolution + /// + [JoinName("InputCurrentResolution")] + public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("InputAudioNames")] - public JoinDataComplete InputAudioNames = - new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 200 }, - new JoinMetadata + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmChassisControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmChassisControllerJoinMap)) { - Description = "DM Chassis Audio Input Names", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Serial - }); - [JoinName("OutputVideoNames")] - public JoinDataComplete OutputVideoNames = - new JoinDataComplete(new JoinData { JoinNumber = 901, JoinSpan = 200 }, - new JoinMetadata + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmChassisControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) { - Description = "DM Chassis Video Output Names", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Serial - }); - [JoinName("OutputAudioNames")] - public JoinDataComplete OutputAudioNames = - new JoinDataComplete(new JoinData { JoinNumber = 1101, JoinSpan = 200 }, - new JoinMetadata - { - Description = "DM Chassis Audio Output Names", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Serial - }); - - [JoinName("OutputCurrentVideoInputNames")] - public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputCurrentAudioInputNames")] - public JoinDataComplete OutputCurrentAudioInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2201, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Audio Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputCurrentResolution")] - public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it - /// - /// Join this join map will start at - public DmChassisControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmChassisControllerJoinMap)) - { - } - - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmChassisControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + } } } diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmRmcControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmRmcControllerJoinMap.cs index 4ddb9ff4..6f0df102 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmRmcControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmRmcControllerJoinMap.cs @@ -1,91 +1,143 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmRmcControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoMuteOn")] - public JoinDataComplete VideoMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Mute Video", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoMuteOff")] - public JoinDataComplete VideoMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC UnMute Video", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoMuteToggle")] - public JoinDataComplete VideoMuteToggle = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Mute Video Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("CurrentOutputResolution")] - public JoinDataComplete CurrentOutputResolution = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Current Output Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EdidManufacturer")] - public JoinDataComplete EdidManufacturer = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC EDID Manufacturer", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EdidName")] - public JoinDataComplete EdidName = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC EDID Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EdidPrefferedTiming")] - public JoinDataComplete EdidPrefferedTiming = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC EDID Preferred Timing", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EdidSerialNumber")] - public JoinDataComplete EdidSerialNumber = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC EDID Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("AudioVideoSource")] - public JoinDataComplete AudioVideoSource = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Audio Video Source Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportCapability")] - public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC HDCP Support Capability", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Port1HdcpState")] - public JoinDataComplete Port1HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC Port 1 (DM) HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Port2HdcpState")] - public JoinDataComplete Port2HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Port 2 (HDMI) HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdmiInputSync")] - public JoinDataComplete HdmiInputSync = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM RMC HDMI Input Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HdcpInputPortCount")] - public JoinDataComplete HdcpInputPortCount = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Number of Input Ports that support HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmRmcControllerJoinMap /// - /// Join this join map will start at - public DmRmcControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmRmcControllerJoinMap)) + public class DmRmcControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// DM RMC Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM RMC Mute Video + /// + [JoinName("VideoMuteOn")] + public JoinDataComplete VideoMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Mute Video", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM RMC UnMute Video + /// + [JoinName("VideoMuteOff")] + public JoinDataComplete VideoMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC UnMute Video", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM RMC Mute Video Toggle + /// + [JoinName("VideoMuteToggle")] + public JoinDataComplete VideoMuteToggle = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Mute Video Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM RMC Current Output Resolution + /// + [JoinName("CurrentOutputResolution")] + public JoinDataComplete CurrentOutputResolution = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Current Output Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC EDID Manufacturer + /// + [JoinName("EdidManufacturer")] + public JoinDataComplete EdidManufacturer = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC EDID Manufacturer", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC EDID Name + /// + [JoinName("EdidName")] + public JoinDataComplete EdidName = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC EDID Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC EDID Preferred Timing + /// + [JoinName("EdidPrefferedTiming")] + public JoinDataComplete EdidPrefferedTiming = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC EDID Preferred Timing", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC EDID Serial Number + /// + [JoinName("EdidSerialNumber")] + public JoinDataComplete EdidSerialNumber = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC EDID Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM RMC Audio Video Source Set / Get + /// + [JoinName("AudioVideoSource")] + public JoinDataComplete AudioVideoSource = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Audio Video Source Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM RMC HDCP Support Capability + /// + [JoinName("HdcpSupportCapability")] + public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC HDCP Support Capability", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM RMC Port 1 (DM) HDCP State Set / Get + /// + [JoinName("Port1HdcpState")] + public JoinDataComplete Port1HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Port 1 (DM) HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM RMC Port 2 (HDMI) HDCP State Set / Get + /// + [JoinName("Port2HdcpState")] + public JoinDataComplete Port2HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC Port 2 (HDMI) HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM RMC HDMI Input Sync + /// + [JoinName("HdmiInputSync")] + public JoinDataComplete HdmiInputSync = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM RMC HDMI Input Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM RMC Number of Input Ports that support HDCP + /// + [JoinName("HdcpInputPortCount")] + public JoinDataComplete HdcpInputPortCount = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Number of Input Ports that support HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmRmcControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmRmcControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmRmcControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmRmcControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmTxControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmTxControllerJoinMap.cs index 8fb99f33..ab1299d1 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmTxControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmTxControllerJoinMap.cs @@ -1,95 +1,150 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmTxControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoSyncStatus")] - public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FreeRunEnabled")] - public JoinDataComplete FreeRunEnabled = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Enable Free Run Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Input1VideoSyncStatus")] - public JoinDataComplete Input1VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Input 1 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Input2VideoSyncStatus")] - public JoinDataComplete Input2VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Input 2 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Input3VideoSyncStatus")] - public JoinDataComplete Input3VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Input 3 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("CurrentInputResolution")] - public JoinDataComplete CurrentInputResolution = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Current Input Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("VideoInput")] - public JoinDataComplete VideoInput = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Video Input Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("AudioInput")] - public JoinDataComplete AudioInput = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Audio Input Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpSupportCapability")] - public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX HDCP Support Capability", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Port1HdcpState")] - public JoinDataComplete Port1HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Port 1 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Port2HdcpState")] - public JoinDataComplete Port2HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Port 2 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("VgaBrightness")] - public JoinDataComplete VgaBrightness = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX VGA Brightness", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("VgaContrast")] - public JoinDataComplete VgaContrast = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Online", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Port3HdcpState")] - public JoinDataComplete Port3HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "DM TX Port 3 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("HdcpInputPortCount")] - public JoinDataComplete HdcpInputPortCount = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "Number of Input Ports that support HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmTxControllerJoinMap /// - /// Join this join map will start at - public DmTxControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmTxControllerJoinMap)) + public class DmTxControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// DM TX Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM TX Video Sync + /// + [JoinName("VideoSyncStatus")] + public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM TX Enable Free Run Set / Get + /// + [JoinName("FreeRunEnabled")] + public JoinDataComplete FreeRunEnabled = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Enable Free Run Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input 1 Video Sync Status + /// + [JoinName("Input1VideoSyncStatus")] + public JoinDataComplete Input1VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Input 1 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input 2 Video Sync Status + /// + [JoinName("Input2VideoSyncStatus")] + public JoinDataComplete Input2VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Input 2 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input 3 Video Sync Status + /// + [JoinName("Input3VideoSyncStatus")] + public JoinDataComplete Input3VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Input 3 Video Sync Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM TX Current Input Resolution + /// + [JoinName("CurrentInputResolution")] + public JoinDataComplete CurrentInputResolution = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Current Input Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM TX Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM TX Video Input Set / Get + /// + [JoinName("VideoInput")] + public JoinDataComplete VideoInput = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Video Input Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX Audio Input Set / Get + /// + [JoinName("AudioInput")] + public JoinDataComplete AudioInput = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Audio Input Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX HDCP Support Capability + /// + [JoinName("HdcpSupportCapability")] + public JoinDataComplete HdcpSupportCapability = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX HDCP Support Capability", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX Port 1 HDCP State Set / Get + /// + [JoinName("Port1HdcpState")] + public JoinDataComplete Port1HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Port 1 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX Port 2 HDCP State Set / Get + /// + [JoinName("Port2HdcpState")] + public JoinDataComplete Port2HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Port 2 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX VGA Brightness + /// + [JoinName("VgaBrightness")] + public JoinDataComplete VgaBrightness = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX VGA Brightness", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX VGA Contrast + /// + [JoinName("VgaContrast")] + public JoinDataComplete VgaContrast = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Online", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX Port 3 HDCP State Set / Get + /// + [JoinName("Port3HdcpState")] + public JoinDataComplete Port3HdcpState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "DM TX Port 3 HDCP State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM TX Number of Input Ports that support HDCP + /// + [JoinName("HdcpInputPortCount")] + public JoinDataComplete HdcpInputPortCount = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "Number of Input Ports that support HDCP", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmTxControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmTxControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmTxControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmTxControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs index 9ad04691..ca1d60b4 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs @@ -1,173 +1,288 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmpsAudioOutputControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("MasterVolumeLevel")] - public JoinDataComplete MasterVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MasterVolumeLevelScaled")] - public JoinDataComplete MasterVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MixerPresetRecall")] - public JoinDataComplete MixerPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Mixer Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MixerEqPresetRecall")] - public JoinDataComplete MixerEqPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Mixer Eq Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MasterVolumeMuteOn")] - public JoinDataComplete MasterVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MasterVolumeMuteOff")] - public JoinDataComplete MasterVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MasterVolumeUp")] - public JoinDataComplete MasterVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MasterVolumeDown")] - public JoinDataComplete MasterVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MasterVolumeLevelScaledSend")] - public JoinDataComplete MasterVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Master Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SourceVolumeLevel")] - public JoinDataComplete SourceVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("SourceVolumeLevelScaled")] - public JoinDataComplete SourceVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("SourceVolumeMuteOn")] - public JoinDataComplete SourceVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SourceVolumeMuteOff")] - public JoinDataComplete SourceVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SourceVolumeUp")] - public JoinDataComplete SourceVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SourceVolumeDown")] - public JoinDataComplete SourceVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SourceVolumeLevelScaledSend")] - public JoinDataComplete SourceVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Source Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec1VolumeLevel")] - public JoinDataComplete Codec1VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Codec1VolumeLevelScaled")] - public JoinDataComplete Codec1VolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Codec1VolumeMuteOn")] - public JoinDataComplete Codec1VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec1VolumeMuteOff")] - public JoinDataComplete Codec1VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec1VolumeUp")] - public JoinDataComplete Codec1VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec1VolumeDown")] - public JoinDataComplete Codec1VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec1VolumeLevelScaledSend")] - public JoinDataComplete Codec1VolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec1 Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec2VolumeLevel")] - public JoinDataComplete Codec2VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Codec2VolumeLevelScaled")] - public JoinDataComplete Codec2VolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Codec2VolumeMuteOn")] - public JoinDataComplete Codec2VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec2VolumeMuteOff")] - public JoinDataComplete Codec2VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec2VolumeUp")] - public JoinDataComplete Codec2VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec2VolumeDown")] - public JoinDataComplete Codec2VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Codec2VolumeLevelScaledSend")] - public JoinDataComplete Codec2VolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, - new JoinMetadata { Description = "Codec2 Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicsMasterVolumeLevel")] - public JoinDataComplete MicsMasterVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MicsMasterVolumeLevelScaled")] - public JoinDataComplete MicsMasterVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MicsMasterVolumeMuteOn")] - public JoinDataComplete MicsMasterVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicsMasterVolumeMuteOff")] - public JoinDataComplete MicsMasterVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicsMasterVolumeUp")] - public JoinDataComplete MicsMasterVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicsMasterVolumeDown")] - public JoinDataComplete MicsMasterVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 44, JoinSpan = 1 }, - new JoinMetadata { Description = "MicsMaster Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicsMasterVolumeLevelScaledSend")] - public JoinDataComplete MicsMasterVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 45, JoinSpan = 1 }, - new JoinMetadata { Description = "Mics Master Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmpsAudioOutputControllerJoinMap /// - /// Join this join map will start at - public DmpsAudioOutputControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmpsAudioOutputControllerJoinMap)) + public class DmpsAudioOutputControllerJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmpsAudioOutputControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// Master Volume Level Signed dB Set / Get + /// + [JoinName("MasterVolumeLevel")] + public JoinDataComplete MasterVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Master Volume 16bit Scaled Set / Get + /// + [JoinName("MasterVolumeLevelScaled")] + public JoinDataComplete MasterVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Mixer Preset Recall Set + /// + [JoinName("MixerPresetRecall")] + public JoinDataComplete MixerPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Mixer Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Mixer Eq Preset Recall Set + /// + [JoinName("MixerEqPresetRecall")] + public JoinDataComplete MixerEqPresetRecall = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Mixer Eq Preset Recall Set", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Master Volume Mute On Set / Get + /// + [JoinName("MasterVolumeMuteOn")] + public JoinDataComplete MasterVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Master Volume Mute Off Set / Get + /// + [JoinName("MasterVolumeMuteOff")] + public JoinDataComplete MasterVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Master Volume Level Up + /// + [JoinName("MasterVolumeUp")] + public JoinDataComplete MasterVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Master Volume Level Down + /// + [JoinName("MasterVolumeDown")] + public JoinDataComplete MasterVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Master Volume Scaled Send Enable/Disable + /// + [JoinName("MasterVolumeLevelScaledSend")] + public JoinDataComplete MasterVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Master Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Source Volume Signed dB Set / Get + /// + [JoinName("SourceVolumeLevel")] + public JoinDataComplete SourceVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Source Volume 16bit Scaled Set / Get + /// + [JoinName("SourceVolumeLevelScaled")] + public JoinDataComplete SourceVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Source Volume Mute On Set / Get + /// + [JoinName("SourceVolumeMuteOn")] + public JoinDataComplete SourceVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Source Volume Mute Off Set / Get + /// + [JoinName("SourceVolumeMuteOff")] + public JoinDataComplete SourceVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Source Volume Level Up + /// + [JoinName("SourceVolumeUp")] + public JoinDataComplete SourceVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Source Volume Level Down + /// + [JoinName("SourceVolumeDown")] + public JoinDataComplete SourceVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Source Volume Scaled Send Enable/Disable + /// + [JoinName("SourceVolumeLevelScaledSend")] + public JoinDataComplete SourceVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Source Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec1 Volume Signed dB Set / Get + /// + [JoinName("Codec1VolumeLevel")] + public JoinDataComplete Codec1VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Codec1 Volume 16bit Scaled Set / Get + /// + [JoinName("Codec1VolumeLevelScaled")] + public JoinDataComplete Codec1VolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Codec1 Volume Mute On Set / Get + /// + [JoinName("Codec1VolumeMuteOn")] + public JoinDataComplete Codec1VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec1 Volume Mute Off Set / Get + /// + [JoinName("Codec1VolumeMuteOff")] + public JoinDataComplete Codec1VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec1 Volume Level Up + /// + [JoinName("Codec1VolumeUp")] + public JoinDataComplete Codec1VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec1 Volume Level Down + /// + [JoinName("Codec1VolumeDown")] + public JoinDataComplete Codec1VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec1 Volume Scaled Send Enable/Disable + /// + [JoinName("Codec1VolumeLevelScaledSend")] + public JoinDataComplete Codec1VolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec1 Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec2 Volume Signed dB Set / Get + /// + [JoinName("Codec2VolumeLevel")] + public JoinDataComplete Codec2VolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Codec2 Volume 16bit Scaled Set / Get + /// + [JoinName("Codec2VolumeLevelScaled")] + public JoinDataComplete Codec2VolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Codec2 Volume Mute On Set / Get + /// + [JoinName("Codec2VolumeMuteOn")] + public JoinDataComplete Codec2VolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec2 Volume Mute Off Set / Get + /// + [JoinName("Codec2VolumeMuteOff")] + public JoinDataComplete Codec2VolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec2 Volume Level Up + /// + [JoinName("Codec2VolumeUp")] + public JoinDataComplete Codec2VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec2 Volume Level Down + /// + [JoinName("Codec2VolumeDown")] + public JoinDataComplete Codec2VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Codec2 Volume Scaled Send Enable/Disable + /// + [JoinName("Codec2VolumeLevelScaledSend")] + public JoinDataComplete Codec2VolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, + new JoinMetadata { Description = "Codec2 Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// MicsMaster Volume Signed dB Set / Get + /// + [JoinName("MicsMasterVolumeLevel")] + public JoinDataComplete MicsMasterVolumeLevel = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume Signed dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// MicsMaster Volume 16bit Scaled Set / Get + /// + [JoinName("MicsMasterVolumeLevelScaled")] + public JoinDataComplete MicsMasterVolumeLevelScaled = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// MicsMaster Volume Mute On Set / Get + /// + [JoinName("MicsMasterVolumeMuteOn")] + public JoinDataComplete MicsMasterVolumeMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// MicsMaster Volume Mute Off Set / Get + /// + [JoinName("MicsMasterVolumeMuteOff")] + public JoinDataComplete MicsMasterVolumeMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// MicsMaster Volume Level Up + /// + [JoinName("MicsMasterVolumeUp")] + public JoinDataComplete MicsMasterVolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume Level Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// MicsMaster Volume Level Down + /// + [JoinName("MicsMasterVolumeDown")] + public JoinDataComplete MicsMasterVolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 44, JoinSpan = 1 }, + new JoinMetadata { Description = "MicsMaster Volume Level Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// MicsMaster Volume Scaled Send Enable/Disable + /// + [JoinName("MicsMasterVolumeLevelScaledSend")] + public JoinDataComplete MicsMasterVolumeLevelScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 45, JoinSpan = 1 }, + new JoinMetadata { Description = "Mics Master Volume Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmpsAudioOutputControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmpsAudioOutputControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmpsAudioOutputControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsMicrophoneControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsMicrophoneControllerJoinMap.cs index 90efc964..3fba12e5 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsMicrophoneControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsMicrophoneControllerJoinMap.cs @@ -1,49 +1,72 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmpsMicrophoneControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("MicGain")] - public JoinDataComplete MicGain = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Gain dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MicGainScaled")] - public JoinDataComplete MicGainScaled = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Gain 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("MicMuteOn")] - public JoinDataComplete MicMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicMuteOff")] - public JoinDataComplete MicMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicGainScaledSend")] - public JoinDataComplete MicGainScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Gain Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("MicName")] - public JoinDataComplete MicName = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Mic Name Get", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmpsMicrophoneControllerJoinMap /// - /// Join this join map will start at - public DmpsMicrophoneControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmpsMicrophoneControllerJoinMap)) + public class DmpsMicrophoneControllerJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmpsMicrophoneControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Mic Gain dB Set / Get + /// + [JoinName("MicGain")] + public JoinDataComplete MicGain = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Gain dB Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Mic Gain 16bit Scaled Set / Get + /// + [JoinName("MicGainScaled")] + public JoinDataComplete MicGainScaled = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Gain 16bit Scaled Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Mic Mute On Set / Get + /// + [JoinName("MicMuteOn")] + public JoinDataComplete MicMuteOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Mute On Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Mic Mute Off Set / Get + /// + [JoinName("MicMuteOff")] + public JoinDataComplete MicMuteOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Mute Off Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Mic Gain Scaled Send Enable/Disable + /// + [JoinName("MicGainScaledSend")] + public JoinDataComplete MicGainScaledSend = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Gain Scaled Send Enable/Disable", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Mic Name Get + /// + [JoinName("MicName")] + public JoinDataComplete MicName = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Mic Name Get", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmpsMicrophoneControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmpsMicrophoneControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmpsMicrophoneControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs index e39e38c6..8e9a3e97 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs @@ -1,122 +1,185 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class DmpsRoutingControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("EnableRouting")] - public JoinDataComplete EnableRouting = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "DMPS Enable Audio and Video Routing", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SystemPowerOn")] - public JoinDataComplete SystemPowerOn = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "DMPS System Power On Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SystemPowerOff")] - public JoinDataComplete SystemPowerOff = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "DMPS System Power Off Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FrontPanelLockOn")] - public JoinDataComplete FrontPanelLockOn = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "DMPS Front Panel Lock On Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FrontPanelLockOff")] - public JoinDataComplete FrontPanelLockOff = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "DMPS Front Panel Lock Off Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoSyncStatus")] - public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputEndpointOnline")] - public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputEndpointOnline")] - public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputVideo")] - public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Video Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutputAudio")] - public JoinDataComplete OutputAudio = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Audio Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InputNames")] - public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputNames")] - public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputVideoNames")] - public JoinDataComplete InputVideoNames = - new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, - new JoinMetadata - { - Description = "Video Input Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); - - [JoinName("InputAudioNames")] - public JoinDataComplete InputAudioNames = - new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, - new JoinMetadata - { - Description = "Audio Input Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); - [JoinName("OutputVideoNames")] - public JoinDataComplete OutputVideoNames = - new JoinDataComplete(new JoinData { JoinNumber = 901, JoinSpan = 32 }, - new JoinMetadata - { - Description = "Video Output Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); - [JoinName("OutputAudioNames")] - public JoinDataComplete OutputAudioNames = - new JoinDataComplete(new JoinData { JoinNumber = 1101, JoinSpan = 32 }, - new JoinMetadata - { - Description = "Audio Output Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); - - [JoinName("OutputCurrentVideoInputNames")] - public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputCurrentAudioInputNames")] - public JoinDataComplete OutputCurrentAudioInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2201, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Audio Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputCurrentResolution")] - public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 32 }, - new JoinMetadata { Description = "DM Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a DmpsRoutingControllerJoinMap /// - /// Join this join map will start at - public DmpsRoutingControllerJoinMap(uint joinStart) - : this(joinStart, typeof(DmpsRoutingControllerJoinMap)) + public class DmpsRoutingControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// DMPS Enable Audio and Video Routing + /// + [JoinName("EnableRouting")] + public JoinDataComplete EnableRouting = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "DMPS Enable Audio and Video Routing", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DMPS Disable Audio and Video Routing + /// + [JoinName("SystemPowerOn")] + public JoinDataComplete SystemPowerOn = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "DMPS System Power On Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DMPS Disable Audio and Video Routing + /// + [JoinName("SystemPowerOff")] + public JoinDataComplete SystemPowerOff = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "DMPS System Power Off Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DMPS Front Panel Lock On Get/Set + /// + [JoinName("FrontPanelLockOn")] + public JoinDataComplete FrontPanelLockOn = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "DMPS Front Panel Lock On Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DMPS Front Panel Lock Off Get/Set + /// + [JoinName("FrontPanelLockOff")] + public JoinDataComplete FrontPanelLockOff = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "DMPS Front Panel Lock Off Get/Set", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Input Video Sync + /// + [JoinName("VideoSyncStatus")] + public JoinDataComplete VideoSyncStatus = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Input Video Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Input Endpoint Online + /// + [JoinName("InputEndpointOnline")] + public JoinDataComplete InputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Output Endpoint Online + /// + [JoinName("OutputEndpointOnline")] + public JoinDataComplete OutputEndpointOnline = new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Endpoint Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DM Chassis Input Video Set / Get + /// + [JoinName("OutputVideo")] + public JoinDataComplete OutputVideo = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Video Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input Audio Set / Get + /// + [JoinName("OutputAudio")] + public JoinDataComplete OutputAudio = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Audio Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// DM Chassis Input Name + /// + [JoinName("InputNames")] + public JoinDataComplete InputNames = new JoinDataComplete(new JoinData { JoinNumber = 101, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Output Name + /// + [JoinName("OutputNames")] + public JoinDataComplete OutputNames = new JoinDataComplete(new JoinData { JoinNumber = 301, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Video Input Name + /// + [JoinName("InputVideoNames")] + public JoinDataComplete InputVideoNames = + new JoinDataComplete(new JoinData { JoinNumber = 501, JoinSpan = 32 }, + new JoinMetadata + { + Description = "DM Chassis Video Input Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Audio Input Name + /// + [JoinName("InputAudioNames")] + public JoinDataComplete InputAudioNames = + new JoinDataComplete(new JoinData { JoinNumber = 701, JoinSpan = 32 }, + new JoinMetadata + { + Description = "Audio Input Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Video Output Name + /// + [JoinName("OutputVideoNames")] + public JoinDataComplete OutputVideoNames = + new JoinDataComplete(new JoinData { JoinNumber = 901, JoinSpan = 32 }, + new JoinMetadata + { + Description = "Video Output Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Audio Output Name + /// + [JoinName("OutputAudioNames")] + public JoinDataComplete OutputAudioNames = + new JoinDataComplete(new JoinData { JoinNumber = 1101, JoinSpan = 32 }, + new JoinMetadata + { + Description = "Audio Output Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// DM Chassis Video Output Currently Routed Video Input Name + /// + [JoinName("OutputCurrentVideoInputNames")] + public JoinDataComplete OutputCurrentVideoInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2001, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Video Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Audio Output Currently Routed Video Input Name + /// + [JoinName("OutputCurrentAudioInputNames")] + public JoinDataComplete OutputCurrentAudioInputNames = new JoinDataComplete(new JoinData { JoinNumber = 2201, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Audio Output Currently Routed Video Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// DM Chassis Input Current Resolution + /// + [JoinName("InputCurrentResolution")] + public JoinDataComplete InputCurrentResolution = new JoinDataComplete(new JoinData { JoinNumber = 2401, JoinSpan = 32 }, + new JoinMetadata { Description = "DM Chassis Input Current Resolution", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected DmpsRoutingControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public DmpsRoutingControllerJoinMap(uint joinStart) + : this(joinStart, typeof(DmpsRoutingControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected DmpsRoutingControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericIrControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericIrControllerJoinMap.cs index 00087187..57977737 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericIrControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericIrControllerJoinMap.cs @@ -1,7 +1,10 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Bridges.JoinMaps; - +namespace PepperDash.Essentials.Core.Bridges.JoinMaps +{ + /// + /// Represents a GenericIrControllerJoinMap + /// public sealed class GenericIrControllerJoinMap : JoinMapBaseAdvanced { /// @@ -992,4 +995,5 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; : base(joinStart, typeof(GenericIrControllerJoinMap)) { } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericLightingJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericLightingJoinMap.cs index 179b4124..2b684d9f 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericLightingJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericLightingJoinMap.cs @@ -1,48 +1,67 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class GenericLightingJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Lighting Controller Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SelectScene")] - public JoinDataComplete SelectScene = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Lighting Controller Select Scene By Index", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SelectSceneDirect")] - public JoinDataComplete SelectSceneDirect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, - new JoinMetadata { Description = "Lighting Controller Select Scene", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalSerial }); - - [JoinName("ButtonVisibility")] - public JoinDataComplete ButtonVisibility = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 10 }, - new JoinMetadata { Description = "Lighting Controller Button Visibility", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IntegrationIdSet")] - public JoinDataComplete IntegrationIdSet = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Lighting Controller Set Integration Id", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); - - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a GenericLightingJoinMap /// - /// Join this join map will start at - public GenericLightingJoinMap(uint joinStart) - : this(joinStart, typeof(GenericLightingJoinMap)) + public class GenericLightingJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected GenericLightingJoinMap(uint joinStart, Type type) : base(joinStart, type) - { + /// + /// Lighting Controller Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Lighting Controller Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Select Scene By Index + /// + [JoinName("SelectScene")] + public JoinDataComplete SelectScene = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Lighting Controller Select Scene By Index", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Select Scene Direct + /// + [JoinName("SelectSceneDirect")] + public JoinDataComplete SelectSceneDirect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 10 }, + new JoinMetadata { Description = "Lighting Controller Select Scene", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalSerial }); + + /// + /// Button Visibility + /// + [JoinName("ButtonVisibility")] + public JoinDataComplete ButtonVisibility = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 10 }, + new JoinMetadata { Description = "Lighting Controller Button Visibility", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Set Integration Id + /// + [JoinName("IntegrationIdSet")] + public JoinDataComplete IntegrationIdSet = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Lighting Controller Set Integration Id", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); + + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public GenericLightingJoinMap(uint joinStart) + : this(joinStart, typeof(GenericLightingJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected GenericLightingJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs index 03aaf041..470dc4e1 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs @@ -1,30 +1,37 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class GenericRelayControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("Relay")] - public JoinDataComplete Relay = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Relay State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a GenericRelayControllerJoinMap /// - /// Join this join map will start at - public GenericRelayControllerJoinMap(uint joinStart) - : this(joinStart, typeof(GenericRelayControllerJoinMap)) + public class GenericRelayControllerJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected GenericRelayControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) - { - + /// + /// Device Relay State Set / Get + /// + [JoinName("Relay")] + public JoinDataComplete Relay = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Relay State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public GenericRelayControllerJoinMap(uint joinStart) + : this(joinStart, typeof(GenericRelayControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected GenericRelayControllerJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs index 228cb4f0..23341e7e 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs @@ -1,184 +1,304 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class GlsOccupancySensorBaseJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ForceOccupied")] - public JoinDataComplete ForceOccupied = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Set to Occupied", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ForceVacant")] - public JoinDataComplete ForceVacant = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Set to Vacant", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableRawStates")] - public JoinDataComplete EnableRawStates = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable Raw", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RoomOccupiedFeedback")] - public JoinDataComplete RoomOccupiedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Room Is Occupied", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("GraceOccupancyDetectedFeedback")] - public JoinDataComplete GraceOccupancyDetectedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Grace Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RoomVacantFeedback")] - public JoinDataComplete RoomVacantFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Room Is Vacant", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyFeedback")] - public JoinDataComplete RawOccupancyFeedback = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Raw Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyPirFeedback")] - public JoinDataComplete RawOccupancyPirFeedback = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Raw PIR Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RawOccupancyUsFeedback")] - public JoinDataComplete RawOccupancyUsFeedback = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Raw US Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableLedFlash")] - public JoinDataComplete EnableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable LED Flash", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableLedFlash")] - public JoinDataComplete DisableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Disable LED Flash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableShortTimeout")] - public JoinDataComplete EnableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable Short Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableShortTimeout")] - public JoinDataComplete DisableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Disable Short Timeout", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OrWhenVacated")] - public JoinDataComplete OrWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Set To Vacant when Either Sensor is Vacant", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("AndWhenVacated")] - public JoinDataComplete AndWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Set To Vacant when Both Sensors are Vacant", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableUsA")] - public JoinDataComplete EnableUsA = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable Ultrasonic Sensor A", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableUsA")] - public JoinDataComplete DisableUsA = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Disable Ultrasonic Sensor A", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnableUsB")] - public JoinDataComplete EnableUsB = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable Ultrasonic Sensor B", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableUsB")] - public JoinDataComplete DisableUsB = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Disable Ultrasonic Sensor B", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("EnablePir")] - public JoinDataComplete EnablePir = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Enable IR Sensor", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisablePir")] - public JoinDataComplete DisablePir = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Disable IR Sensor", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementUsInOccupiedState")] - public JoinDataComplete IncrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Increment US Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementUsInOccupiedState")] - public JoinDataComplete DecrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Decrement US Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementUsInVacantState")] - public JoinDataComplete IncrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Increment US Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementUsInVacantState")] - public JoinDataComplete DecrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Decrement US Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementPirInOccupiedState")] - public JoinDataComplete IncrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Increment IR Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementPirInOccupiedState")] - public JoinDataComplete DecrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Decrement IR Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IncrementPirInVacantState")] - public JoinDataComplete IncrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Increment IR Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DecrementPirInVacantState")] - public JoinDataComplete DecrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Decrement IR Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Timeout")] - public JoinDataComplete Timeout = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Timeout Value", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("TimeoutLocalFeedback")] - public JoinDataComplete TimeoutLocalFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Local Timeout Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("InternalPhotoSensorValue")] - public JoinDataComplete InternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Internal PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("ExternalPhotoSensorValue")] - public JoinDataComplete ExternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor External PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("UsSensitivityInOccupiedState")] - public JoinDataComplete UsSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Ultrasonic Sensitivity in Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("UsSensitivityInVacantState")] - public JoinDataComplete UsSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Ultrasonic Sensitivity in Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("PirSensitivityInOccupiedState")] - public JoinDataComplete PirSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor PIR Sensitivity in Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("PirSensitivityInVacantState")] - public JoinDataComplete PirSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor PIR Sensitivity in Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Occ Sensor Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a GlsOccupancySensorBaseJoinMap /// - /// Join this join map will start at - public GlsOccupancySensorBaseJoinMap(uint joinStart) - : this(joinStart, typeof(GlsOccupancySensorBaseJoinMap)) + public class GlsOccupancySensorBaseJoinMap : JoinMapBaseAdvanced { + /// + /// Occ Sensor Is Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Set to Occupied + /// + [JoinName("ForceOccupied")] + public JoinDataComplete ForceOccupied = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Set to Occupied", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Set to Vacant + /// + [JoinName("ForceVacant")] + public JoinDataComplete ForceVacant = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Set to Vacant", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable Raw + /// + [JoinName("EnableRawStates")] + public JoinDataComplete EnableRawStates = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable Raw", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable Raw + /// + [JoinName("RoomOccupiedFeedback")] + public JoinDataComplete RoomOccupiedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Room Is Occupied", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Grace Occupancy Detected + /// + [JoinName("GraceOccupancyDetectedFeedback")] + public JoinDataComplete GraceOccupancyDetectedFeedback = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Grace Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Room Is Vacant + /// + [JoinName("RoomVacantFeedback")] + public JoinDataComplete RoomVacantFeedback = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Room Is Vacant", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Raw Occupancy Detected + /// + [JoinName("RawOccupancyFeedback")] + public JoinDataComplete RawOccupancyFeedback = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Raw Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Raw PIR Occupancy Detected + /// + [JoinName("RawOccupancyPirFeedback")] + public JoinDataComplete RawOccupancyPirFeedback = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Raw PIR Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Raw US Occupancy Detected + /// + [JoinName("RawOccupancyUsFeedback")] + public JoinDataComplete RawOccupancyUsFeedback = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Raw US Occupancy Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable LED Flash + /// + [JoinName("EnableLedFlash")] + public JoinDataComplete EnableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable LED Flash", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable LED Flash + /// + [JoinName("DisableLedFlash")] + public JoinDataComplete DisableLedFlash = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Disable LED Flash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable Short Timeout + /// + [JoinName("EnableShortTimeout")] + public JoinDataComplete EnableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable Short Timeout", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable Short Timeout + /// + [JoinName("DisableShortTimeout")] + public JoinDataComplete DisableShortTimeout = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Disable Short Timeout", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Set To Vacant when Either Sensor is Vacant + /// + [JoinName("OrWhenVacated")] + public JoinDataComplete OrWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Set To Vacant when Either Sensor is Vacant", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Set To Vacant when Both Sensors are Vacant + /// + [JoinName("AndWhenVacated")] + public JoinDataComplete AndWhenVacated = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Set To Vacant when Both Sensors are Vacant", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable Ultrasonic Sensor A + /// + [JoinName("EnableUsA")] + public JoinDataComplete EnableUsA = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable Ultrasonic Sensor A", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable Ultrasonic Sensor A + /// + [JoinName("DisableUsA")] + public JoinDataComplete DisableUsA = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Disable Ultrasonic Sensor A", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable Ultrasonic Sensor B + /// + [JoinName("EnableUsB")] + public JoinDataComplete EnableUsB = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable Ultrasonic Sensor B", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable Ultrasonic Sensor B + /// + [JoinName("DisableUsB")] + public JoinDataComplete DisableUsB = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Disable Ultrasonic Sensor B", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Enable IR Sensor + /// + [JoinName("EnablePir")] + public JoinDataComplete EnablePir = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Enable IR Sensor", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Disable IR Sensor + /// + [JoinName("DisablePir")] + public JoinDataComplete DisablePir = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Disable IR Sensor", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Increment US Occupied State Sensitivity + /// + [JoinName("IncrementUsInOccupiedState")] + public JoinDataComplete IncrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Increment US Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Decrement US Occupied State Sensitivity + /// + [JoinName("DecrementUsInOccupiedState")] + public JoinDataComplete DecrementUsInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Decrement US Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Increment US Vacant State Sensitivity + /// + [JoinName("IncrementUsInVacantState")] + public JoinDataComplete IncrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Increment US Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Decrement US Vacant State Sensitivity + /// + [JoinName("DecrementUsInVacantState")] + public JoinDataComplete DecrementUsInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Decrement US Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Increment IR Occupied State Sensitivity + /// + [JoinName("IncrementPirInOccupiedState")] + public JoinDataComplete IncrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Increment IR Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Decrement IR Occupied State Sensitivity + /// + [JoinName("DecrementPirInOccupiedState")] + public JoinDataComplete DecrementPirInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Decrement IR Occupied State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Increment IR Vacant State Sensitivity + /// + [JoinName("IncrementPirInVacantState")] + public JoinDataComplete IncrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Increment IR Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Decrement IR Vacant State Sensitivity + /// + [JoinName("DecrementPirInVacantState")] + public JoinDataComplete DecrementPirInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Decrement IR Vacant State Sensitivity", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Occ Sensor Timeout Value + /// + [JoinName("Timeout")] + public JoinDataComplete Timeout = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Timeout Value", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor Local Timeout Value + /// + [JoinName("TimeoutLocalFeedback")] + public JoinDataComplete TimeoutLocalFeedback = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Local Timeout Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor Internal PhotoSensor Value + /// + [JoinName("InternalPhotoSensorValue")] + public JoinDataComplete InternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Internal PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor External PhotoSensor Value + /// + [JoinName("ExternalPhotoSensorValue")] + public JoinDataComplete ExternalPhotoSensorValue = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor External PhotoSensor Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor Ultrasonic Sensitivity in Occupied State + /// + [JoinName("UsSensitivityInOccupiedState")] + public JoinDataComplete UsSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Ultrasonic Sensitivity in Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor Ultrasonic Sensitivity in Vacant State + /// + [JoinName("UsSensitivityInVacantState")] + public JoinDataComplete UsSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Ultrasonic Sensitivity in Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor PIR Sensitivity in Occupied State + /// + [JoinName("PirSensitivityInOccupiedState")] + public JoinDataComplete PirSensitivityInOccupiedState = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor PIR Sensitivity in Occupied State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor PIR Sensitivity in Vacant State + /// + [JoinName("PirSensitivityInVacantState")] + public JoinDataComplete PirSensitivityInVacantState = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor PIR Sensitivity in Vacant State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Occ Sensor Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Occ Sensor Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public GlsOccupancySensorBaseJoinMap(uint joinStart) + : this(joinStart, typeof(GlsOccupancySensorBaseJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected GlsOccupancySensorBaseJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } + } - - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected GlsOccupancySensorBaseJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { - } - -} - +} diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsPartitionSensorJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsPartitionSensorJoinMap.cs index 7515c8c2..98c491dd 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsPartitionSensorJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/GlsPartitionSensorJoinMap.cs @@ -1,156 +1,183 @@ using System; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Bridges.JoinMaps; - -public class GlsPartitionSensorJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges.JoinMaps { - - #region Digital - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete( - new JoinData - { - JoinNumber = 1, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Is Online", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Digital - }); - - - [JoinName("Enable")] - public JoinDataComplete Enable = new JoinDataComplete( - new JoinData - { - JoinNumber = 2, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Enable", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("PartitionSensed")] - public JoinDataComplete PartitionSensed = new JoinDataComplete( - new JoinData - { - JoinNumber = 3, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Partition Sensed", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("PartitionNotSensed")] - public JoinDataComplete PartitionNotSensed = new JoinDataComplete( - new JoinData - { - JoinNumber = 4, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Partition Not Sensed", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("IncreaseSensitivity")] - public JoinDataComplete IncreaseSensitivity = new JoinDataComplete( - new JoinData - { - JoinNumber = 6, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Increase Sensitivity", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - [JoinName("DecreaseSensitivity")] - public JoinDataComplete DecreaseSensitivity = new JoinDataComplete( - new JoinData - { - JoinNumber = 7, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Decrease Sensitivity", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); - - #endregion - - #region Analog - - [JoinName("Sensitivity")] - public JoinDataComplete Sensitivity = new JoinDataComplete( - new JoinData - { - JoinNumber = 2, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Sensitivity", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Analog - }); - - #endregion - - - #region Serial - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete( - new JoinData - { - JoinNumber = 1, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sensor Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); - - #endregion - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a GlsPartitionSensorJoinMap /// - /// Join this join map will start at - public GlsPartitionSensorJoinMap(uint joinStart) - : this(joinStart, typeof(GlsPartitionSensorJoinMap)) + public class GlsPartitionSensorJoinMap : JoinMapBaseAdvanced { - } + #region Digital - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected GlsPartitionSensorJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Sensor Is Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Is Online", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + /// + /// Sensor Enable + /// + [JoinName("Enable")] + public JoinDataComplete Enable = new JoinDataComplete( + new JoinData + { + JoinNumber = 2, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Enable", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// Sensor Partition Sensed + /// + [JoinName("PartitionSensed")] + public JoinDataComplete PartitionSensed = new JoinDataComplete( + new JoinData + { + JoinNumber = 3, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Partition Sensed", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// Sensor Partition Not Sensed + /// + [JoinName("PartitionNotSensed")] + public JoinDataComplete PartitionNotSensed = new JoinDataComplete( + new JoinData + { + JoinNumber = 4, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Partition Not Sensed", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// Sensor Increase Sensitivity + /// + [JoinName("IncreaseSensitivity")] + public JoinDataComplete IncreaseSensitivity = new JoinDataComplete( + new JoinData + { + JoinNumber = 6, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Increase Sensitivity", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + /// + /// Sensor Decrease Sensitivity + /// + [JoinName("DecreaseSensitivity")] + public JoinDataComplete DecreaseSensitivity = new JoinDataComplete( + new JoinData + { + JoinNumber = 7, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Decrease Sensitivity", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); + + #endregion + + #region Analog + + /// + /// Sensor Sensitivity + /// + [JoinName("Sensitivity")] + public JoinDataComplete Sensitivity = new JoinDataComplete( + new JoinData + { + JoinNumber = 2, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Sensitivity", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + + #endregion + + + #region Serial + + /// + /// Sensor Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete( + new JoinData + { + JoinNumber = 1, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sensor Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + #endregion + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public GlsPartitionSensorJoinMap(uint joinStart) + : this(joinStart, typeof(GlsPartitionSensorJoinMap)) + { + + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected GlsPartitionSensorJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + + } } } diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdNxM4kEControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdNxM4kEControllerJoinMap.cs index 43ed25c9..950e3f1e 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdNxM4kEControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdNxM4kEControllerJoinMap.cs @@ -1,65 +1,99 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class HdMdNxM4kEControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EnableAutoRoute")] - public JoinDataComplete EnableAutoRoute = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Enable Automatic Routing on 4x1 Switchers", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputName")] - public JoinDataComplete InputName = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 8 }, - new JoinMetadata { Description = "Device Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("InputSync")] - public JoinDataComplete InputSync = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 8 }, - new JoinMetadata { Description = "Device Input Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutputName")] - public JoinDataComplete OutputName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 2 }, - new JoinMetadata { Description = "Device Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutputRoute")] - public JoinDataComplete OutputRoute = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 2 }, - new JoinMetadata { Description = "Device Output Route Set/Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutputRoutedName")] - public JoinDataComplete OutputRoutedName = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 2 }, - new JoinMetadata { Description = "Device Output Route Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("EnableInputHdcp")] - public JoinDataComplete EnableInputHdcp = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 8 }, - new JoinMetadata { Description = "Device Enable Input Hdcp", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DisableInputHdcp")] - public JoinDataComplete DisableInputHdcp = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 8 }, - new JoinMetadata { Description = "Device Disnable Input Hdcp", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Onlne", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a HdMdNxM4kEControllerJoinMap /// - /// Join this join map will start at - public HdMdNxM4kEControllerJoinMap(uint joinStart) - : this(joinStart, typeof(HdMdNxM4kEControllerJoinMap)) + public class HdMdNxM4kEControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Device Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected HdMdNxM4kEControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Enable Automatic Routing on 4x1 Switchers + /// + [JoinName("EnableAutoRoute")] + public JoinDataComplete EnableAutoRoute = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Enable Automatic Routing on 4x1 Switchers", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Device Input Name + /// + [JoinName("InputName")] + public JoinDataComplete InputName = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 8 }, + new JoinMetadata { Description = "Device Input Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Device Input Sync + /// + [JoinName("InputSync")] + public JoinDataComplete InputSync = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 8 }, + new JoinMetadata { Description = "Device Input Sync", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Device Output Name + /// + [JoinName("OutputName")] + public JoinDataComplete OutputName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 2 }, + new JoinMetadata { Description = "Device Output Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Device Output Route Set/Get + /// + [JoinName("OutputRoute")] + public JoinDataComplete OutputRoute = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 2 }, + new JoinMetadata { Description = "Device Output Route Set/Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Device Output Route Name + /// + [JoinName("OutputRoutedName")] + public JoinDataComplete OutputRoutedName = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 2 }, + new JoinMetadata { Description = "Device Output Route Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Device Enable Input Hdcp + /// + [JoinName("EnableInputHdcp")] + public JoinDataComplete EnableInputHdcp = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 8 }, + new JoinMetadata { Description = "Device Enable Input Hdcp", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Device Disable Input Hdcp + /// + [JoinName("DisableInputHdcp")] + public JoinDataComplete DisableInputHdcp = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 8 }, + new JoinMetadata { Description = "Device Disnable Input Hdcp", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Device Online Status + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Onlne", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public HdMdNxM4kEControllerJoinMap(uint joinStart) + : this(joinStart, typeof(HdMdNxM4kEControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected HdMdNxM4kEControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs index 32bc5651..2730a0ea 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs @@ -1,75 +1,114 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class HdMdxxxCEControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RemoteEndDetected")] - public JoinDataComplete RemoteEndDetected = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Remote End Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("AutoRouteOn")] - public JoinDataComplete AutoRouteOn = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Auto Route On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("AutoRouteOff")] - public JoinDataComplete AutoRouteOff = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Auto Route Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PriorityRoutingOn")] - public JoinDataComplete PriorityRoutingOn = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Priority Routing On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PriorityRoutingOff")] - public JoinDataComplete PriorityRoutingOff = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Priority Routing Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputOnScreenDisplayEnabled")] - public JoinDataComplete InputOnScreenDisplayEnabled = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Input OSD Enabled", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("InputOnScreenDisplayDisabled")] - public JoinDataComplete InputOnScreenDisplayDisabled = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Device Input OSD Disabled", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("SyncDetected")] - public JoinDataComplete SyncDetected = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 5 }, - new JoinMetadata { Description = "Device Sync Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VideoSource")] - public JoinDataComplete VideoSource = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 5 }, - new JoinMetadata { Description = "Device Video Source Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("SourceCount")] - public JoinDataComplete SourceCount = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 5 }, - new JoinMetadata { Description = "Device Video Source Count", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("SourceNames")] - public JoinDataComplete SourceNames = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 5 }, - new JoinMetadata { Description = "Device Video Source Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a HdMdxxxCEControllerJoinMap /// - /// Join this join map will start at - public HdMdxxxCEControllerJoinMap(uint joinStart) - : this(joinStart, typeof(HdMdxxxCEControllerJoinMap)) + public class HdMdxxxCEControllerJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected HdMdxxxCEControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Device Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Remote End Detected + /// + [JoinName("RemoteEndDetected")] + public JoinDataComplete RemoteEndDetected = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Remote End Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Auto Route On + /// + [JoinName("AutoRouteOn")] + public JoinDataComplete AutoRouteOn = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Auto Route On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Auto Route Off + /// + [JoinName("AutoRouteOff")] + public JoinDataComplete AutoRouteOff = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Auto Route Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Priority Routing On + /// + [JoinName("PriorityRoutingOn")] + public JoinDataComplete PriorityRoutingOn = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Priority Routing On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Priority Routing Off + /// + [JoinName("PriorityRoutingOff")] + public JoinDataComplete PriorityRoutingOff = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Priority Routing Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input On Screen Display Enabled + /// + [JoinName("InputOnScreenDisplayEnabled")] + public JoinDataComplete InputOnScreenDisplayEnabled = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Input OSD Enabled", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Input On Screen Display Disabled + /// + [JoinName("InputOnScreenDisplayDisabled")] + public JoinDataComplete InputOnScreenDisplayDisabled = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Device Input OSD Disabled", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Sync Detected + /// + [JoinName("SyncDetected")] + public JoinDataComplete SyncDetected = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 5 }, + new JoinMetadata { Description = "Device Sync Detected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Video Source + /// + [JoinName("VideoSource")] + public JoinDataComplete VideoSource = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 5 }, + new JoinMetadata { Description = "Device Video Source Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Source Count + /// + [JoinName("SourceCount")] + public JoinDataComplete SourceCount = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 5 }, + new JoinMetadata { Description = "Device Video Source Count", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + /// + /// Source Names + /// + [JoinName("SourceNames")] + public JoinDataComplete SourceNames = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 5 }, + new JoinMetadata { Description = "Device Video Source Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public HdMdxxxCEControllerJoinMap(uint joinStart) + : this(joinStart, typeof(HdMdxxxCEControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected HdMdxxxCEControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs index e5225ac6..aee3c2f1 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/HdPsXxxControllerJoinMap.cs @@ -1,8 +1,11 @@ using System; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Bridges; - +namespace PepperDash.Essentials.Core.Bridges +{ + /// + /// Represents a HdPsXxxControllerJoinMap + /// public class HdPsXxxControllerJoinMap : JoinMapBaseAdvanced { @@ -209,4 +212,5 @@ namespace PepperDash.Essentials.Core.Bridges; : base(joinStart, type) { } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/Hrxxx0WirelessRemoteControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/Hrxxx0WirelessRemoteControllerJoinMap.cs index ca9ef3c6..5c4c858e 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/Hrxxx0WirelessRemoteControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/Hrxxx0WirelessRemoteControllerJoinMap.cs @@ -1,245 +1,414 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class Hrxxx0WirelessRemoteControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("Power")] - public JoinDataComplete Power = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Power", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Menu")] - public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Menu", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Guide")] - public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Guide", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Info")] - public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Info", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeUp")] - public JoinDataComplete VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "VolumeUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("VolumeDown")] - public JoinDataComplete VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "VolumeDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DialPadUp")] - public JoinDataComplete DialPadUp = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "DialPadUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DialPadDown")] - public JoinDataComplete DialPadDown = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "DialPadDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DialPadLeft")] - public JoinDataComplete DialPadLeft = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "DialPadLeft", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DialPadRight")] - public JoinDataComplete DialPadRight = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, - new JoinMetadata { Description = "DialPadRight", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DialPadSelect")] - public JoinDataComplete DialPadSelect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "DialPadSelect", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelUp")] - public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "ChannelUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelDown")] - public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "ChannelDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Mute")] - public JoinDataComplete Mute = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Mute", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Exit")] - public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Exit", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Last")] - public JoinDataComplete Last = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, - new JoinMetadata { Description = "Last", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Play")] - public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, - new JoinMetadata { Description = "Play", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Pause")] - public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, - new JoinMetadata { Description = "Pause", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Rewind")] - public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, - new JoinMetadata { Description = "Rewind", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FastForward")] - public JoinDataComplete FastForward = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, - new JoinMetadata { Description = "FastForward", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PreviousTrack")] - public JoinDataComplete PreviousTrack = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "PreviousTrack", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("NextTrack")] - public JoinDataComplete NextTrack = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "NextTrack", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Stop")] - public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "Stop", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Record")] - public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "Record", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Dvr")] - public JoinDataComplete Dvr = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "Dvr", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad1")] - public JoinDataComplete Keypad1 = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad2Abc")] - public JoinDataComplete Keypad2 = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad2Abc", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad3Def")] - public JoinDataComplete Keypad3Def = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad3Def", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad4Ghi")] - public JoinDataComplete Keypad4Ghi = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad4Ghi", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad5Jkl")] - public JoinDataComplete Keypad5Jkl = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad5Jkl", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad6Mno")] - public JoinDataComplete Keypad6Mno = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad6Mno", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad7Pqrs")] - public JoinDataComplete Keypad7Pqrs = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad7Pqrs", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad8Tuv")] - public JoinDataComplete Keypad8Tuv = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad8Tuv", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad9Wxyz")] - public JoinDataComplete Keypad9Wxyz = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad9Wxyz", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Keypad0")] - public JoinDataComplete Keypad0 = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad0", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Clear")] - public JoinDataComplete Clear = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, - new JoinMetadata { Description = "Clear", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Enter")] - public JoinDataComplete Enter = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, - new JoinMetadata { Description = "Enter", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Red")] - public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, - new JoinMetadata { Description = "Red", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Green")] - public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, - new JoinMetadata { Description = "Green", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Yellow")] - public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, - new JoinMetadata { Description = "Yellow", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Blue")] - public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, - new JoinMetadata { Description = "Blue", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom1")] - public JoinDataComplete Custom1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom2")] - public JoinDataComplete Custom2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom3")] - public JoinDataComplete Custom3 = new JoinDataComplete(new JoinData { JoinNumber = 44, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom3", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom4")] - public JoinDataComplete Custom4 = new JoinDataComplete(new JoinData { JoinNumber = 45, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom4", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom5")] - public JoinDataComplete Custom5 = new JoinDataComplete(new JoinData { JoinNumber = 46, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom5", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom6")] - public JoinDataComplete Custom6 = new JoinDataComplete(new JoinData { JoinNumber = 47, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom6", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom7")] - public JoinDataComplete Custom7 = new JoinDataComplete(new JoinData { JoinNumber = 48, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom7", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom8")] - public JoinDataComplete Custom8 = new JoinDataComplete(new JoinData { JoinNumber = 49, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom8", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Custom9")] - public JoinDataComplete Custom9 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, - new JoinMetadata { Description = "Custom9", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Fav")] - public JoinDataComplete Fav = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1 }, - new JoinMetadata { Description = "Fav", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Home")] - public JoinDataComplete Home = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1 }, - new JoinMetadata { Description = "Home", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("BatteryLow")] - public JoinDataComplete BatteryLow = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1 }, - new JoinMetadata { Description = "BatteryLow", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("BatteryCritical")] - public JoinDataComplete BatteryCritical = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1 }, - new JoinMetadata { Description = "BatteryCritical", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("BatteryVoltage")] - public JoinDataComplete BatteryVoltage = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "BatteryVoltage", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a Hrxxx0WirelessRemoteControllerJoinMap /// - /// Join this join map will start at - public Hrxxx0WirelessRemoteControllerJoinMap(uint joinStart) - : this(joinStart, typeof(Hrxxx0WirelessRemoteControllerJoinMap)) + public class Hrxxx0WirelessRemoteControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Power + /// + [JoinName("Power")] + public JoinDataComplete Power = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Power", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected Hrxxx0WirelessRemoteControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Menu + /// + [JoinName("Menu")] + public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Menu", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Guide + /// + [JoinName("Guide")] + public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Guide", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Info + /// + [JoinName("Info")] + public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Info", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// VolumeUp + /// + [JoinName("VolumeUp")] + public JoinDataComplete VolumeUp = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "VolumeUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// VolumeDown + /// + [JoinName("VolumeDown")] + public JoinDataComplete VolumeDown = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "VolumeDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DialPadUp + /// + [JoinName("DialPadUp")] + public JoinDataComplete DialPadUp = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "DialPadUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DialPadDown + /// + [JoinName("DialPadDown")] + public JoinDataComplete DialPadDown = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "DialPadDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DialPadLeft + /// + [JoinName("DialPadLeft")] + public JoinDataComplete DialPadLeft = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "DialPadLeft", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DialPadRight + /// + [JoinName("DialPadRight")] + public JoinDataComplete DialPadRight = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, + new JoinMetadata { Description = "DialPadRight", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// DialPadSelect + /// + [JoinName("DialPadSelect")] + public JoinDataComplete DialPadSelect = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "DialPadSelect", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// ChannelUp + /// + [JoinName("ChannelUp")] + public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "ChannelUp", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// ChannelDown + /// + [JoinName("ChannelDown")] + public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "ChannelDown", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Mute + /// + [JoinName("Mute")] + public JoinDataComplete Mute = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Mute", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Exit + /// + [JoinName("Exit")] + public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Exit", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Last + /// + [JoinName("Last")] + public JoinDataComplete Last = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, + new JoinMetadata { Description = "Last", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Play + /// + [JoinName("Play")] + public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, + new JoinMetadata { Description = "Play", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Pause + /// + [JoinName("Pause")] + public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, + new JoinMetadata { Description = "Pause", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Rewind + /// + [JoinName("Rewind")] + public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, + new JoinMetadata { Description = "Rewind", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// FastForward + /// + [JoinName("FastForward")] + public JoinDataComplete FastForward = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, + new JoinMetadata { Description = "FastForward", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// PreviousTrack + /// + [JoinName("PreviousTrack")] + public JoinDataComplete PreviousTrack = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "PreviousTrack", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// NextTrack + /// + [JoinName("NextTrack")] + public JoinDataComplete NextTrack = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "NextTrack", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Stop + /// + [JoinName("Stop")] + public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "Stop", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Record + /// + [JoinName("Record")] + public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "Record", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Dvr + /// + [JoinName("Dvr")] + public JoinDataComplete Dvr = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "Dvr", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad1 + /// + [JoinName("Keypad1")] + public JoinDataComplete Keypad1 = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad2Abc + /// + [JoinName("Keypad2Abc")] + public JoinDataComplete Keypad2 = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad2Abc", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad3Def + /// + [JoinName("Keypad3Def")] + public JoinDataComplete Keypad3Def = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad3Def", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad4Ghi + /// + [JoinName("Keypad4Ghi")] + public JoinDataComplete Keypad4Ghi = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad4Ghi", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad5Jkl + /// + [JoinName("Keypad5Jkl")] + public JoinDataComplete Keypad5Jkl = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad5Jkl", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad6Mno + /// + [JoinName("Keypad6Mno")] + public JoinDataComplete Keypad6Mno = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad6Mno", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad7Pqrs + /// + [JoinName("Keypad7Pqrs")] + public JoinDataComplete Keypad7Pqrs = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad7Pqrs", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad8Tuv + /// + [JoinName("Keypad8Tuv")] + public JoinDataComplete Keypad8Tuv = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad8Tuv", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad9Wxyz + /// + [JoinName("Keypad9Wxyz")] + public JoinDataComplete Keypad9Wxyz = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad9Wxyz", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad0 + /// + [JoinName("Keypad0")] + public JoinDataComplete Keypad0 = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad0", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Clear + /// + [JoinName("Clear")] + public JoinDataComplete Clear = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, + new JoinMetadata { Description = "Clear", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Enter + /// + [JoinName("Enter")] + public JoinDataComplete Enter = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, + new JoinMetadata { Description = "Enter", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Red + /// + [JoinName("Red")] + public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, + new JoinMetadata { Description = "Red", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Green + /// + [JoinName("Green")] + public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, + new JoinMetadata { Description = "Green", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Yellow + /// + [JoinName("Yellow")] + public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, + new JoinMetadata { Description = "Yellow", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Blue + /// + [JoinName("Blue")] + public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, + new JoinMetadata { Description = "Blue", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom1 + /// + [JoinName("Custom1")] + public JoinDataComplete Custom1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom2 + /// + [JoinName("Custom2")] + public JoinDataComplete Custom2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom3 + /// + [JoinName("Custom3")] + public JoinDataComplete Custom3 = new JoinDataComplete(new JoinData { JoinNumber = 44, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom3", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom4 + /// + [JoinName("Custom4")] + public JoinDataComplete Custom4 = new JoinDataComplete(new JoinData { JoinNumber = 45, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom4", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom5 + /// + [JoinName("Custom5")] + public JoinDataComplete Custom5 = new JoinDataComplete(new JoinData { JoinNumber = 46, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom5", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom6 + /// + [JoinName("Custom6")] + public JoinDataComplete Custom6 = new JoinDataComplete(new JoinData { JoinNumber = 47, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom6", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom7 + /// + [JoinName("Custom7")] + public JoinDataComplete Custom7 = new JoinDataComplete(new JoinData { JoinNumber = 48, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom7", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom8 + /// + [JoinName("Custom8")] + public JoinDataComplete Custom8 = new JoinDataComplete(new JoinData { JoinNumber = 49, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom8", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Custom9 + /// + [JoinName("Custom9")] + public JoinDataComplete Custom9 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, + new JoinMetadata { Description = "Custom9", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Fav + /// + [JoinName("Fav")] + public JoinDataComplete Fav = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1 }, + new JoinMetadata { Description = "Fav", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Home + /// + [JoinName("Home")] + public JoinDataComplete Home = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1 }, + new JoinMetadata { Description = "Home", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// BatteryLow + /// + [JoinName("BatteryLow")] + public JoinDataComplete BatteryLow = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1 }, + new JoinMetadata { Description = "BatteryLow", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// BatteryCritical + /// + [JoinName("BatteryCritical")] + public JoinDataComplete BatteryCritical = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1 }, + new JoinMetadata { Description = "BatteryCritical", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// BatteryVoltage + /// + [JoinName("BatteryVoltage")] + public JoinDataComplete BatteryVoltage = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "BatteryVoltage", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public Hrxxx0WirelessRemoteControllerJoinMap(uint joinStart) + : this(joinStart, typeof(Hrxxx0WirelessRemoteControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected Hrxxx0WirelessRemoteControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IAnalogInputJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IAnalogInputJoinMap.cs index 80509ecc..59f90347 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IAnalogInputJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IAnalogInputJoinMap.cs @@ -1,33 +1,44 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class IAnalogInputJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("InputValue")] - public JoinDataComplete InputValue = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Input Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - [JoinName("MinimumChange")] - public JoinDataComplete MinimumChange = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Minimum voltage change required to reflect a change", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a IAnalogInputJoinMap /// - /// Join this join map will start at - public IAnalogInputJoinMap(uint joinStart) - : this(joinStart, typeof(IAnalogInputJoinMap)) + public class IAnalogInputJoinMap : JoinMapBaseAdvanced { - } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected IAnalogInputJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Input Value + /// + [JoinName("InputValue")] + public JoinDataComplete InputValue = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Input Value", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Minimum Change + /// + [JoinName("MinimumChange")] + public JoinDataComplete MinimumChange = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Minimum voltage change required to reflect a change", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IAnalogInputJoinMap(uint joinStart) + : this(joinStart, typeof(IAnalogInputJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IAnalogInputJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs index 1db5b1ff..b4c473cb 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs @@ -1,49 +1,71 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class IBasicCommunicationJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("TextReceived")] - public JoinDataComplete TextReceived = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Text Received From Remote Device", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("SendText")] - public JoinDataComplete SendText = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Text Sent To Remote Device", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("SetPortConfig")] - public JoinDataComplete SetPortConfig = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Set Port Config", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("Connect")] - public JoinDataComplete Connect = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Connect", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Connected")] - public JoinDataComplete Connected = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Status")] - public JoinDataComplete Status = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a IBasicCommunicationJoinMap /// - /// Join this join map will start at - public IBasicCommunicationJoinMap(uint joinStart) - : this(joinStart, typeof(IBasicCommunicationJoinMap)) + public class IBasicCommunicationJoinMap : JoinMapBaseAdvanced { - } + /// + /// Text Received From Remote Device + /// + [JoinName("TextReceived")] + public JoinDataComplete TextReceived = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Text Received From Remote Device", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected IBasicCommunicationJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Text Sent To Remote Device + /// + [JoinName("SendText")] + public JoinDataComplete SendText = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Text Sent To Remote Device", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Set Port Config + /// + [JoinName("SetPortConfig")] + public JoinDataComplete SetPortConfig = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Set Port Config", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Connect + /// + [JoinName("Connect")] + public JoinDataComplete Connect = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Connect", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Disconnect + /// + [JoinName("Connected")] + public JoinDataComplete Connected = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Status + /// + [JoinName("Status")] + public JoinDataComplete Status = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IBasicCommunicationJoinMap(uint joinStart) + : this(joinStart, typeof(IBasicCommunicationJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IBasicCommunicationJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalInputJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalInputJoinMap.cs index ceed325d..0f77fce3 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalInputJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalInputJoinMap.cs @@ -1,30 +1,36 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class IDigitalInputJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("InputState")] - public JoinDataComplete InputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Input State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a IDigitalInputJoinMap /// - /// Join this join map will start at - public IDigitalInputJoinMap(uint joinStart) - : this(joinStart, typeof(IDigitalInputJoinMap)) + public class IDigitalInputJoinMap : JoinMapBaseAdvanced { - } + /// + /// Input State + /// + [JoinName("InputState")] + public JoinDataComplete InputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Input State", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected IDigitalInputJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IDigitalInputJoinMap(uint joinStart) + : this(joinStart, typeof(IDigitalInputJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IDigitalInputJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalOutputJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalOutputJoinMap.cs index 8abb2b74..05895acb 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalOutputJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IDigitalOutputJoinMap.cs @@ -1,30 +1,36 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class IDigitalOutputJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - - [JoinName("OutputState")] - public JoinDataComplete OutputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Get / Set state of Digital Input", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a IDigitalOutputJoinMap /// - /// Join this join map will start at - public IDigitalOutputJoinMap(uint joinStart) - : this(joinStart, typeof(IDigitalOutputJoinMap)) + public class IDigitalOutputJoinMap : JoinMapBaseAdvanced { - } + /// + /// Output State + /// + [JoinName("OutputState")] + public JoinDataComplete OutputState = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Get / Set state of Digital Input", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected IDigitalOutputJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IDigitalOutputJoinMap(uint joinStart) + : this(joinStart, typeof(IDigitalOutputJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IDigitalOutputJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IRBlurayBaseJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IRBlurayBaseJoinMap.cs index d63a1f52..3e66eda6 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IRBlurayBaseJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/IRBlurayBaseJoinMap.cs @@ -4,218 +4,359 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.Bridges; - -/// -/// Join map for IRBlurayBase devices -/// -public class IRBlurayBaseJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("PowerOn")] - public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PowerOff")] - public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PowerToggle")] - public JoinDataComplete PowerToggle = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Power Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Up")] - public JoinDataComplete Up = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Down")] - public JoinDataComplete Down = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Left")] - public JoinDataComplete Left = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Right")] - public JoinDataComplete Right = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Select")] - public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Menu")] - public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Exit")] - public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, - new JoinMetadata { Description = "Exit", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit0")] - public JoinDataComplete Digit0 = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 0", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit1")] - public JoinDataComplete Digit1 = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 1", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit2")] - public JoinDataComplete Digit2 = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 2", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit3")] - public JoinDataComplete Digit3 = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 3", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit4")] - public JoinDataComplete Digit4 = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 4", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit5")] - public JoinDataComplete Digit5 = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 5", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit6")] - public JoinDataComplete Digit6 = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 6", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit7")] - public JoinDataComplete Digit7 = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 7", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit8")] - public JoinDataComplete Digit8 = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 8", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit9")] - public JoinDataComplete Digit9 = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, - new JoinMetadata { Description = "Digit 9", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadClear")] - public JoinDataComplete KeypadClear = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad Clear", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadEnter")] - public JoinDataComplete KeypadEnter = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad Enter", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelUp")] - public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Channel Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelDown")] - public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Channel Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("LastChannel")] - public JoinDataComplete LastChannel = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "Last Channel", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Guide")] - public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, - new JoinMetadata { Description = "Guide", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Info")] - public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, - new JoinMetadata { Description = "Info", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Red")] - public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, - new JoinMetadata { Description = "Red", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Green")] - public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, - new JoinMetadata { Description = "Green", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Yellow")] - public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "Yellow", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Blue")] - public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, - new JoinMetadata { Description = "Blue", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - - [JoinName("Play")] - public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, - new JoinMetadata { Description = "Play", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Pause")] - public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, - new JoinMetadata { Description = "Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Stop")] - public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, - new JoinMetadata { Description = "Stop", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FFwd")] - public JoinDataComplete FFwd = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, - new JoinMetadata { Description = "FFwd", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Rewind")] - public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, - new JoinMetadata { Description = "Rewind", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChapPlus")] - public JoinDataComplete ChapPlus = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, - new JoinMetadata { Description = "Chapter Plus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChapMinus")] - public JoinDataComplete ChapMinus = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, - new JoinMetadata { Description = "Chapter Minus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Replay")] - public JoinDataComplete Replay = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, - new JoinMetadata { Description = "Replay", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Record")] - public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, - new JoinMetadata { Description = "Record", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasKeypadAccessoryButton1")] - public JoinDataComplete HasKeypadAccessoryButton1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "Has Keypad Accessory Button 1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasKeypadAccessoryButton2")] - public JoinDataComplete HasKeypadAccessoryButton2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "Has Keypad Accessory Button 2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton1Press")] - public JoinDataComplete KeypadAccessoryButton1Press = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 2 }, - new JoinMetadata { Description = "Keypad Accessory Button 1 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton2Press")] - public JoinDataComplete KeypadAccessoryButton2Press = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 2 }, - new JoinMetadata { Description = "Keypad Accessory Button 2 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton1Label")] - public JoinDataComplete KeypadAccessoryButton1Label = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("KeypadAccessoryButton2Label")] - public JoinDataComplete KeypadAccessoryButton2Label = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it - /// - /// Join this join map will start at - public IRBlurayBaseJoinMap(uint joinStart) - : this(joinStart, typeof(IRBlurayBaseJoinMap)) - { - } - /// - /// Constructor to use when extending this Join map + /// Join map for IRBlurayBase devices /// - /// Join this join map will start at - /// Type of the child join map - protected IRBlurayBaseJoinMap(uint joinStart, Type type) - : base(joinStart, type) + public class IRBlurayBaseJoinMap : JoinMapBaseAdvanced { + /// + /// Power On + /// + [JoinName("PowerOn")] + public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Power On", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Power Off + /// + [JoinName("PowerOff")] + public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Power Toggle + /// + [JoinName("PowerToggle")] + public JoinDataComplete PowerToggle = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Power Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Nav Up + /// + [JoinName("Up")] + public JoinDataComplete Up = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Nav Down + /// + [JoinName("Down")] + public JoinDataComplete Down = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Nav Left + /// + [JoinName("Left")] + public JoinDataComplete Left = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Nav Right + /// + [JoinName("Right")] + public JoinDataComplete Right = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Select + /// + [JoinName("Select")] + public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Menu + /// + [JoinName("Menu")] + public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Exit + /// + [JoinName("Exit")] + public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, + new JoinMetadata { Description = "Exit", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 0 + /// + [JoinName("Digit0")] + public JoinDataComplete Digit0 = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 0", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 1 + /// + [JoinName("Digit1")] + public JoinDataComplete Digit1 = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 1", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 2 + /// + [JoinName("Digit2")] + public JoinDataComplete Digit2 = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 2", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 3 + /// + [JoinName("Digit3")] + public JoinDataComplete Digit3 = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 3", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 4 + /// + [JoinName("Digit4")] + public JoinDataComplete Digit4 = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 4", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 5 + /// + [JoinName("Digit5")] + public JoinDataComplete Digit5 = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 5", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 6 + /// + [JoinName("Digit6")] + public JoinDataComplete Digit6 = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 6", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 7 + /// + [JoinName("Digit7")] + public JoinDataComplete Digit7 = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 7", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 8 + /// + [JoinName("Digit8")] + public JoinDataComplete Digit8 = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 8", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Digit 9 + /// + [JoinName("Digit9")] + public JoinDataComplete Digit9 = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, + new JoinMetadata { Description = "Digit 9", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad Clear + /// + [JoinName("KeypadClear")] + public JoinDataComplete KeypadClear = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad Clear", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad Enter + /// + [JoinName("KeypadEnter")] + public JoinDataComplete KeypadEnter = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad Enter", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Channel Up + /// + [JoinName("ChannelUp")] + public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Channel Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Channel Down + /// + [JoinName("ChannelDown")] + public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Channel Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Last Channel + /// + [JoinName("LastChannel")] + public JoinDataComplete LastChannel = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "Last Channel", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Guide + /// + [JoinName("Guide")] + public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, + new JoinMetadata { Description = "Guide", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Info + /// + [JoinName("Info")] + public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, + new JoinMetadata { Description = "Info", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Red + /// + [JoinName("Red")] + public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, + new JoinMetadata { Description = "Red", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Green + /// + [JoinName("Green")] + public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, + new JoinMetadata { Description = "Green", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Yellow + /// + [JoinName("Yellow")] + public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "Yellow", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Blue + /// + [JoinName("Blue")] + public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, + new JoinMetadata { Description = "Blue", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Play + /// + [JoinName("Play")] + public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, + new JoinMetadata { Description = "Play", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Pause + /// + [JoinName("Pause")] + public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, + new JoinMetadata { Description = "Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Stop + /// + [JoinName("Stop")] + public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, + new JoinMetadata { Description = "Stop", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Fast Forward + /// + [JoinName("FFwd")] + public JoinDataComplete FFwd = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, + new JoinMetadata { Description = "FFwd", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Rewind + /// + [JoinName("Rewind")] + public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, + new JoinMetadata { Description = "Rewind", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Chapter Plus + /// + [JoinName("ChapPlus")] + public JoinDataComplete ChapPlus = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, + new JoinMetadata { Description = "Chapter Plus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Chapter Minus + /// + [JoinName("ChapMinus")] + public JoinDataComplete ChapMinus = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, + new JoinMetadata { Description = "Chapter Minus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Replay + /// + [JoinName("Replay")] + public JoinDataComplete Replay = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, + new JoinMetadata { Description = "Replay", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Record + /// + [JoinName("Record")] + public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, + new JoinMetadata { Description = "Record", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Has Keypad Accessory Button 1 + /// + [JoinName("HasKeypadAccessoryButton1")] + public JoinDataComplete HasKeypadAccessoryButton1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "Has Keypad Accessory Button 1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Has Keypad Accessory Button 2 + /// + [JoinName("HasKeypadAccessoryButton2")] + public JoinDataComplete HasKeypadAccessoryButton2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "Has Keypad Accessory Button 2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad Accessory Button 1 Press + /// + [JoinName("KeypadAccessoryButton1Press")] + public JoinDataComplete KeypadAccessoryButton1Press = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 2 }, + new JoinMetadata { Description = "Keypad Accessory Button 1 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad Accessory Button 2 Press + /// + [JoinName("KeypadAccessoryButton2Press")] + public JoinDataComplete KeypadAccessoryButton2Press = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 2 }, + new JoinMetadata { Description = "Keypad Accessory Button 2 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Keypad Accessory Button 1 Label + /// + [JoinName("KeypadAccessoryButton1Label")] + public JoinDataComplete KeypadAccessoryButton1Label = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Keypad Accessory Button 2 Label + /// + [JoinName("KeypadAccessoryButton2Label")] + public JoinDataComplete KeypadAccessoryButton2Label = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public IRBlurayBaseJoinMap(uint joinStart) + : this(joinStart, typeof(IRBlurayBaseJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected IRBlurayBaseJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/PduJoinMapBase.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/PduJoinMapBase.cs index 10bf590d..f52b96c4 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/PduJoinMapBase.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/PduJoinMapBase.cs @@ -1,59 +1,87 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class PduJoinMapBase : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "PDU Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("Online")] - public JoinDataComplete Online = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutletCount")] - public JoinDataComplete OutletCount = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Number of COntrolled Outlets", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("OutletName")] - public JoinDataComplete OutletName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Outlet Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("OutletEnabled")] - public JoinDataComplete OutletEnabled = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Outlet Enabled", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutletPowerCycle")] - public JoinDataComplete OutletPowerCycle = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Outlet Power Cycle", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutletPowerOn")] - public JoinDataComplete OutletPowerOn = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Outlet Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("OutletPowerOff")] - public JoinDataComplete OutletPowerOff = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Outlet Power Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a PduJoinMapBase /// - /// Join this join map will start at - public PduJoinMapBase(uint joinStart) - :base(joinStart, typeof(PduJoinMapBase)) - { - } + public class PduJoinMapBase : JoinMapBaseAdvanced + { + /// + /// PDU Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "PDU Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - public PduJoinMapBase(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// PDU Online Status + /// + [JoinName("Online")] + public JoinDataComplete Online = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Number of Controlled Outlets + /// + [JoinName("OutletCount")] + public JoinDataComplete OutletCount = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Number of COntrolled Outlets", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Outlet Name + /// + [JoinName("OutletName")] + public JoinDataComplete OutletName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// Outlet Enabled Status + /// + [JoinName("OutletEnabled")] + public JoinDataComplete OutletEnabled = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Enabled", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Outlet Power State + /// + [JoinName("OutletPowerCycle")] + public JoinDataComplete OutletPowerCycle = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power Cycle", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Outlet Power On + /// + [JoinName("OutletPowerOn")] + public JoinDataComplete OutletPowerOn = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Outlet Power Off + /// + [JoinName("OutletPowerOff")] + public JoinDataComplete OutletPowerOff = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Outlet Power Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public PduJoinMapBase(uint joinStart) + :base(joinStart, typeof(PduJoinMapBase)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + public PduJoinMapBase(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SetTopBoxControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SetTopBoxControllerJoinMap.cs index 0705eb93..ce9a4290 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SetTopBoxControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SetTopBoxControllerJoinMap.cs @@ -1,239 +1,402 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class SetTopBoxControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("PowerOn")] - public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Power On", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PowerOff")] - public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("PowerToggle")] - public JoinDataComplete PowerToggle = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Power Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasDpad")] - public JoinDataComplete HasDpad = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Has DPad", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Up")] - public JoinDataComplete Up = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Down")] - public JoinDataComplete Down = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Left")] - public JoinDataComplete Left = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Right")] - public JoinDataComplete Right = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Select")] - public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Menu")] - public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Exit")] - public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Exit", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasNumeric")] - public JoinDataComplete HasNumeric = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Has Numeric", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit0")] - public JoinDataComplete Digit0 = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 0", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit1")] - public JoinDataComplete Digit1 = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 1", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit2")] - public JoinDataComplete Digit2 = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 2", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit3")] - public JoinDataComplete Digit3 = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 3", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit4")] - public JoinDataComplete Digit4 = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 4", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit5")] - public JoinDataComplete Digit5 = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 5", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit6")] - public JoinDataComplete Digit6 = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 6", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit7")] - public JoinDataComplete Digit7 = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 7", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit8")] - public JoinDataComplete Digit8 = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 8", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Digit9")] - public JoinDataComplete Digit9 = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Digit 9", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Dash")] - public JoinDataComplete Dash = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Dash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadEnter")] - public JoinDataComplete KeypadEnter = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Keypad Enter", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelUp")] - public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Channel Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChannelDown")] - public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Channel Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("LastChannel")] - public JoinDataComplete LastChannel = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Last Channel", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Guide")] - public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Guide", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Info")] - public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Info", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Red")] - public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Red", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Green")] - public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Green", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Yellow")] - public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Yellow", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Blue")] - public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Blue", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasDvr")] - public JoinDataComplete HasDvr = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Has DVR", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("DvrList")] - public JoinDataComplete DvrList = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, - new JoinMetadata { Description = "STB DvrList", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Play")] - public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Play", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Pause")] - public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Stop")] - public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Stop", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("FFwd")] - public JoinDataComplete FFwd = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, - new JoinMetadata { Description = "STB FFwd", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Rewind")] - public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Rewind", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChapPlus")] - public JoinDataComplete ChapPlus = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Chapter Plus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("ChapMinus")] - public JoinDataComplete ChapMinus = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Chapter Minus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Replay")] - public JoinDataComplete Replay = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Replay", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Record")] - public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Record", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasKeypadAccessoryButton1")] - public JoinDataComplete HasKeypadAccessoryButton1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Has Keypad Accessory Button 1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasKeypadAccessoryButton2")] - public JoinDataComplete HasKeypadAccessoryButton2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Has Keypad Accessory Button 2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton1Press")] - public JoinDataComplete KeypadAccessoryButton1Press = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 2 }, - new JoinMetadata { Description = "STB Keypad Accessory Button 1 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton2Press")] - public JoinDataComplete KeypadAccessoryButton2Press = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 2 }, - new JoinMetadata { Description = "STB Keypad Accessory Button 2 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("KeypadAccessoryButton1Label")] - public JoinDataComplete KeypadAccessoryButton1Label = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("KeypadAccessoryButton2Label")] - public JoinDataComplete KeypadAccessoryButton2Label = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("LoadPresets")] - public JoinDataComplete LoadPresets = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Load Presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("HasPresets")] - public JoinDataComplete HasPresets = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, - new JoinMetadata { Description = "STB Load Presets", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a SetTopBoxControllerJoinMap /// - /// Join this join map will start at - public SetTopBoxControllerJoinMap(uint joinStart) - : this(joinStart, typeof(SetTopBoxControllerJoinMap)) + public class SetTopBoxControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// STB Power On + /// + [JoinName("PowerOn")] + public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Power On", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected SetTopBoxControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// STB Power Off + /// + [JoinName("PowerOff")] + public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Power Toggle + /// + [JoinName("PowerToggle")] + public JoinDataComplete PowerToggle = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Power Toggle", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has DPad + /// + [JoinName("HasDpad")] + public JoinDataComplete HasDpad = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Has DPad", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Nav Up + /// + [JoinName("Up")] + public JoinDataComplete Up = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Nav Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Nav Down + /// + [JoinName("Down")] + public JoinDataComplete Down = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Nav Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Nav Left + /// + [JoinName("Left")] + public JoinDataComplete Left = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Nav Left", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Nav Right + /// + [JoinName("Right")] + public JoinDataComplete Right = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Nav Right", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Select + /// + [JoinName("Select")] + public JoinDataComplete Select = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Select", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Menu + /// + [JoinName("Menu")] + public JoinDataComplete Menu = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Menu", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Exit + /// + [JoinName("Exit")] + public JoinDataComplete Exit = new JoinDataComplete(new JoinData { JoinNumber = 10, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Exit", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has Numeric + /// + [JoinName("HasNumeric")] + public JoinDataComplete HasNumeric = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Has Numeric", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 0 + /// + [JoinName("Digit0")] + public JoinDataComplete Digit0 = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 0", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 1 + /// + [JoinName("Digit1")] + public JoinDataComplete Digit1 = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 1", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 2 + /// + [JoinName("Digit2")] + public JoinDataComplete Digit2 = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 2", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 3 + /// + [JoinName("Digit3")] + public JoinDataComplete Digit3 = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 3", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 4 + /// + [JoinName("Digit4")] + public JoinDataComplete Digit4 = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 4", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 5 + /// + [JoinName("Digit5")] + public JoinDataComplete Digit5 = new JoinDataComplete(new JoinData { JoinNumber = 16, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 5", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 6 + /// + [JoinName("Digit6")] + public JoinDataComplete Digit6 = new JoinDataComplete(new JoinData { JoinNumber = 17, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 6", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 7 + /// + [JoinName("Digit7")] + public JoinDataComplete Digit7 = new JoinDataComplete(new JoinData { JoinNumber = 18, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 7", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 8 + /// + [JoinName("Digit8")] + public JoinDataComplete Digit8 = new JoinDataComplete(new JoinData { JoinNumber = 19, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 8", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Digit 9 + /// + [JoinName("Digit9")] + public JoinDataComplete Digit9 = new JoinDataComplete(new JoinData { JoinNumber = 20, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Digit 9", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Dash + /// + [JoinName("Dash")] + public JoinDataComplete Dash = new JoinDataComplete(new JoinData { JoinNumber = 21, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Dash", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Keypad Enter + /// + [JoinName("KeypadEnter")] + public JoinDataComplete KeypadEnter = new JoinDataComplete(new JoinData { JoinNumber = 22, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Keypad Enter", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Channel Up + /// + [JoinName("ChannelUp")] + public JoinDataComplete ChannelUp = new JoinDataComplete(new JoinData { JoinNumber = 23, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Channel Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Channel Down + /// + [JoinName("ChannelDown")] + public JoinDataComplete ChannelDown = new JoinDataComplete(new JoinData { JoinNumber = 24, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Channel Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Last Channel + /// + [JoinName("LastChannel")] + public JoinDataComplete LastChannel = new JoinDataComplete(new JoinData { JoinNumber = 25, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Last Channel", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Guide + /// + [JoinName("Guide")] + public JoinDataComplete Guide = new JoinDataComplete(new JoinData { JoinNumber = 26, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Guide", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Info + /// + [JoinName("Info")] + public JoinDataComplete Info = new JoinDataComplete(new JoinData { JoinNumber = 27, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Info", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Red + /// + [JoinName("Red")] + public JoinDataComplete Red = new JoinDataComplete(new JoinData { JoinNumber = 28, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Red", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Green + /// + [JoinName("Green")] + public JoinDataComplete Green = new JoinDataComplete(new JoinData { JoinNumber = 29, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Green", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Yellow + /// + [JoinName("Yellow")] + public JoinDataComplete Yellow = new JoinDataComplete(new JoinData { JoinNumber = 30, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Yellow", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Blue + /// + [JoinName("Blue")] + public JoinDataComplete Blue = new JoinDataComplete(new JoinData { JoinNumber = 31, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Blue", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has DVR + /// + [JoinName("HasDvr")] + public JoinDataComplete HasDvr = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Has DVR", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Dvr List + /// + [JoinName("DvrList")] + public JoinDataComplete DvrList = new JoinDataComplete(new JoinData { JoinNumber = 32, JoinSpan = 1 }, + new JoinMetadata { Description = "STB DvrList", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Play + /// + [JoinName("Play")] + public JoinDataComplete Play = new JoinDataComplete(new JoinData { JoinNumber = 33, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Play", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Pause + /// + [JoinName("Pause")] + public JoinDataComplete Pause = new JoinDataComplete(new JoinData { JoinNumber = 34, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Pause", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Stop + /// + [JoinName("Stop")] + public JoinDataComplete Stop = new JoinDataComplete(new JoinData { JoinNumber = 35, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Stop", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB FFwd + /// + [JoinName("FFwd")] + public JoinDataComplete FFwd = new JoinDataComplete(new JoinData { JoinNumber = 36, JoinSpan = 1 }, + new JoinMetadata { Description = "STB FFwd", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Rewind + /// + [JoinName("Rewind")] + public JoinDataComplete Rewind = new JoinDataComplete(new JoinData { JoinNumber = 37, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Rewind", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Chapter Plus + /// + [JoinName("ChapPlus")] + public JoinDataComplete ChapPlus = new JoinDataComplete(new JoinData { JoinNumber = 38, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Chapter Plus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Chapter Minus + /// + [JoinName("ChapMinus")] + public JoinDataComplete ChapMinus = new JoinDataComplete(new JoinData { JoinNumber = 39, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Chapter Minus", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Replay + /// + [JoinName("Replay")] + public JoinDataComplete Replay = new JoinDataComplete(new JoinData { JoinNumber = 40, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Replay", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Record + /// + [JoinName("Record")] + public JoinDataComplete Record = new JoinDataComplete(new JoinData { JoinNumber = 41, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Record", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has Keypad Accessory Button 1 + /// + [JoinName("HasKeypadAccessoryButton1")] + public JoinDataComplete HasKeypadAccessoryButton1 = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Has Keypad Accessory Button 1", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has Keypad Accessory Button 2 + /// + [JoinName("HasKeypadAccessoryButton2")] + public JoinDataComplete HasKeypadAccessoryButton2 = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Has Keypad Accessory Button 2", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Keypad Accessory Button 1 Press + /// + [JoinName("KeypadAccessoryButton1Press")] + public JoinDataComplete KeypadAccessoryButton1Press = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 2 }, + new JoinMetadata { Description = "STB Keypad Accessory Button 1 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Keypad Accessory Button 2 Press + /// + [JoinName("KeypadAccessoryButton2Press")] + public JoinDataComplete KeypadAccessoryButton2Press = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 2 }, + new JoinMetadata { Description = "STB Keypad Accessory Button 2 Press", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Keypad Accessory Button 1 Label + /// + [JoinName("KeypadAccessoryButton1Label")] + public JoinDataComplete KeypadAccessoryButton1Label = new JoinDataComplete(new JoinData { JoinNumber = 42, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// STB Keypad Accessory Button 2 Label + /// + [JoinName("KeypadAccessoryButton2Label")] + public JoinDataComplete KeypadAccessoryButton2Label = new JoinDataComplete(new JoinData { JoinNumber = 43, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Keypad Accessory Button 1 Label", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + + /// + /// STB Load Presets + /// + [JoinName("LoadPresets")] + public JoinDataComplete LoadPresets = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Load Presets", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// STB Has Presets + /// + [JoinName("HasPresets")] + public JoinDataComplete HasPresets = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1 }, + new JoinMetadata { Description = "STB Load Presets", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); + + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public SetTopBoxControllerJoinMap(uint joinStart) + : this(joinStart, typeof(SetTopBoxControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected SetTopBoxControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/StatusSignControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/StatusSignControllerJoinMap.cs index 98cd9218..79ada2c8 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/StatusSignControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/StatusSignControllerJoinMap.cs @@ -1,58 +1,86 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class StatusSignControllerJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("IsOnline")] - public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Sign Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("Name")] - public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Sign Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - - [JoinName("RedControl")] - public JoinDataComplete RedControl = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Red LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("RedLed")] - public JoinDataComplete RedLed = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Red LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("GreenControl")] - public JoinDataComplete GreenControl = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Green LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("GreenLed")] - public JoinDataComplete GreenLed = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Green LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - - [JoinName("BlueControl")] - public JoinDataComplete BlueControl = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Blue LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - - [JoinName("BlueLed")] - public JoinDataComplete BlueLed = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Status Blue LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it + /// Represents a StatusSignControllerJoinMap /// - /// Join this join map will start at - public StatusSignControllerJoinMap(uint joinStart) - : this(joinStart, typeof(StatusSignControllerJoinMap)) + public class StatusSignControllerJoinMap : JoinMapBaseAdvanced { - } + /// + /// Status Sign Online + /// + [JoinName("IsOnline")] + public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Sign Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected StatusSignControllerJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { - } + /// + /// Status Sign Name + /// + [JoinName("Name")] + public JoinDataComplete Name = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Sign Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Red LED Control + /// + [JoinName("RedControl")] + public JoinDataComplete RedControl = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Red LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Red LED Intensity + /// + [JoinName("RedLed")] + public JoinDataComplete RedLed = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Red LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Green LED Control + /// + [JoinName("GreenControl")] + public JoinDataComplete GreenControl = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Green LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Green LED Intensity + /// + [JoinName("GreenLed")] + public JoinDataComplete GreenLed = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Green LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Blue LED Control + /// + [JoinName("BlueControl")] + public JoinDataComplete BlueControl = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Blue LED Enable / Disable", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + + /// + /// Blue LED Intensity + /// + [JoinName("BlueLed")] + public JoinDataComplete BlueLed = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Status Blue LED Intensity", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public StatusSignControllerJoinMap(uint joinStart) + : this(joinStart, typeof(StatusSignControllerJoinMap)) + { + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected StatusSignControllerJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SystemMonitorJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SystemMonitorJoinMap.cs index 3d9ac81b..c53c9fe6 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SystemMonitorJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/SystemMonitorJoinMap.cs @@ -1,136 +1,235 @@ using System; -namespace PepperDash.Essentials.Core.Bridges; - -public class SystemMonitorJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Bridges { - [JoinName("TimeZone")] - public JoinDataComplete TimeZone = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Timezone", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); + /// + /// Represents a SystemMonitorJoinMap + /// + public class SystemMonitorJoinMap : JoinMapBaseAdvanced + { + /// + /// Processor Timezone + /// + [JoinName("TimeZone")] + public JoinDataComplete TimeZone = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Timezone", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - [JoinName("TimeZoneName")] - public JoinDataComplete TimeZoneName = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Timezone Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Timezone Name + /// + [JoinName("TimeZoneName")] + public JoinDataComplete TimeZoneName = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Timezone Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("IOControllerVersion")] - public JoinDataComplete IOControllerVersion = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor IO Controller Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor OS Version + /// + [JoinName("IOControllerVersion")] + public JoinDataComplete IOControllerVersion = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor IO Controller Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("SnmpAppVersion")] - public JoinDataComplete SnmpAppVersion = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor SNMP App Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor SNMP App Version + /// + [JoinName("SnmpAppVersion")] + public JoinDataComplete SnmpAppVersion = new JoinDataComplete(new JoinData { JoinNumber = 3, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor SNMP App Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("BACnetAppVersion")] - public JoinDataComplete BACnetAppVersion = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor BACNet App Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor BACNet App Version + /// + [JoinName("BACnetAppVersion")] + public JoinDataComplete BACnetAppVersion = new JoinDataComplete(new JoinData { JoinNumber = 4, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor BACNet App Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("ControllerVersion")] - public JoinDataComplete ControllerVersion = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Controller Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Controller Version + /// + [JoinName("ControllerVersion")] + public JoinDataComplete ControllerVersion = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Controller Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("SerialNumber")] - public JoinDataComplete SerialNumber = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Serial Number + /// + [JoinName("SerialNumber")] + public JoinDataComplete SerialNumber = new JoinDataComplete(new JoinData { JoinNumber = 6, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Serial Number", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("Model")] - public JoinDataComplete Model = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Model", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Model + /// + [JoinName("Model")] + public JoinDataComplete Model = new JoinDataComplete(new JoinData { JoinNumber = 7, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Model", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("Uptime")] - public JoinDataComplete Uptime = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Uptime", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Uptime + /// + [JoinName("Uptime")] + public JoinDataComplete Uptime = new JoinDataComplete(new JoinData { JoinNumber = 8, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Uptime", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("LastBoot")] - public JoinDataComplete LastBoot = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Last Boot", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Last Boot Time + /// + [JoinName("LastBoot")] + public JoinDataComplete LastBoot = new JoinDataComplete(new JoinData { JoinNumber = 9, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Last Boot", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("ProgramOffsetJoin")] - public JoinDataComplete ProgramOffsetJoin = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 5 }, - new JoinMetadata { Description = "All Program Data is offset between slots by 5 - First Joins Start at 11", JoinCapabilities = eJoinCapabilities.None, JoinType = eJoinType.None }); + /// + /// Program Offset Join + /// + [JoinName("ProgramOffsetJoin")] + public JoinDataComplete ProgramOffsetJoin = new JoinDataComplete(new JoinData { JoinNumber = 5, JoinSpan = 5 }, + new JoinMetadata { Description = "All Program Data is offset between slots by 5 - First Joins Start at 11", JoinCapabilities = eJoinCapabilities.None, JoinType = eJoinType.None }); - [JoinName("ProgramStart")] - public JoinDataComplete ProgramStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Start / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + /// + /// Processor Program Start + /// + [JoinName("ProgramStart")] + public JoinDataComplete ProgramStart = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Start / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ProgramStop")] - public JoinDataComplete ProgramStop = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Stop / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + /// + /// Processor Program Stop + /// + [JoinName("ProgramStop")] + public JoinDataComplete ProgramStop = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Stop / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ProgramRegister")] - public JoinDataComplete ProgramRegister = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Register / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + /// + /// Processor Program Register + /// + [JoinName("ProgramRegister")] + public JoinDataComplete ProgramRegister = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Register / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ProgramUnregister")] - public JoinDataComplete ProgramUnregister = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program UnRegister / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); + /// + /// Processor Program Unregister + /// + [JoinName("ProgramUnregister")] + public JoinDataComplete ProgramUnregister = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program UnRegister / Fb", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital }); - [JoinName("ProgramName")] - public JoinDataComplete ProgramName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Program Name + /// + [JoinName("ProgramName")] + public JoinDataComplete ProgramName = new JoinDataComplete(new JoinData { JoinNumber = 11, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("ProgramCompiledTime")] - public JoinDataComplete ProgramCompiledTime = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Compile Time", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Program Version + /// + [JoinName("ProgramCompiledTime")] + public JoinDataComplete ProgramCompiledTime = new JoinDataComplete(new JoinData { JoinNumber = 12, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Compile Time", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("ProgramCrestronDatabaseVersion")] - public JoinDataComplete ProgramCrestronDatabaseVersion = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Database Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Program Crestron Database Version + /// + [JoinName("ProgramCrestronDatabaseVersion")] + public JoinDataComplete ProgramCrestronDatabaseVersion = new JoinDataComplete(new JoinData { JoinNumber = 13, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Database Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("ProgramEnvironmentVersion")] - public JoinDataComplete ProgramEnvironmentVersion = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Environment Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Program Environment Version + /// + [JoinName("ProgramEnvironmentVersion")] + public JoinDataComplete ProgramEnvironmentVersion = new JoinDataComplete(new JoinData { JoinNumber = 14, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Environment Version", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("AggregatedProgramInfo")] - public JoinDataComplete AggregatedProgramInfo = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Program Aggregate Info Json", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Program Aggregate Info + /// + [JoinName("AggregatedProgramInfo")] + public JoinDataComplete AggregatedProgramInfo = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Program Aggregate Info Json", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("EthernetOffsetJoin")] - public JoinDataComplete EthernetOffsetJoin = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, - new JoinMetadata { Description = "All Ethernet Data is offset between Nics by 5 - First Joins Start at 76", JoinCapabilities = eJoinCapabilities.None, JoinType = eJoinType.None }); + /// + /// Ethernet Offset Join + /// + [JoinName("EthernetOffsetJoin")] + public JoinDataComplete EthernetOffsetJoin = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 }, + new JoinMetadata { Description = "All Ethernet Data is offset between Nics by 5 - First Joins Start at 76", JoinCapabilities = eJoinCapabilities.None, JoinType = eJoinType.None }); - [JoinName("HostName")] - public JoinDataComplete HostName = new JoinDataComplete(new JoinData { JoinNumber = 76, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Hostname", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Hostname + /// + [JoinName("HostName")] + public JoinDataComplete HostName = new JoinDataComplete(new JoinData { JoinNumber = 76, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Hostname", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("CurrentIpAddress")] - public JoinDataComplete CurrentIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 77, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Current Ip Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Current Ip Address + /// + [JoinName("CurrentIpAddress")] + public JoinDataComplete CurrentIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 77, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Current Ip Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("CurrentSubnetMask")] - public JoinDataComplete CurrentSubnetMask = new JoinDataComplete(new JoinData { JoinNumber = 78, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Current Subnet Mask", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Current Subnet Mask + /// + [JoinName("CurrentSubnetMask")] + public JoinDataComplete CurrentSubnetMask = new JoinDataComplete(new JoinData { JoinNumber = 78, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Current Subnet Mask", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("CurrentDefaultGateway")] - public JoinDataComplete CurrentDefaultGateway = new JoinDataComplete(new JoinData { JoinNumber = 79, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Current Default Gateway", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Current Default Gateway + /// + [JoinName("CurrentDefaultGateway")] + public JoinDataComplete CurrentDefaultGateway = new JoinDataComplete(new JoinData { JoinNumber = 79, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Current Default Gateway", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("StaticIpAddress")] - public JoinDataComplete StaticIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 80, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Static Ip Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Static Ip Address + /// + [JoinName("StaticIpAddress")] + public JoinDataComplete StaticIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 80, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Static Ip Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("StaticSubnetMask")] - public JoinDataComplete StaticSubnetMask = new JoinDataComplete(new JoinData { JoinNumber = 81, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Static Subnet Mask", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Static Subnet Mask + /// + [JoinName("StaticSubnetMask")] + public JoinDataComplete StaticSubnetMask = new JoinDataComplete(new JoinData { JoinNumber = 81, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Static Subnet Mask", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("StaticDefaultGateway")] - public JoinDataComplete StaticDefaultGateway = new JoinDataComplete(new JoinData { JoinNumber = 82, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Static Default Gateway", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Static Default Gateway + /// + [JoinName("StaticDefaultGateway")] + public JoinDataComplete StaticDefaultGateway = new JoinDataComplete(new JoinData { JoinNumber = 82, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Static Default Gateway", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("Domain")] - public JoinDataComplete Domain = new JoinDataComplete(new JoinData { JoinNumber = 83, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Domain", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Domain + /// + [JoinName("Domain")] + public JoinDataComplete Domain = new JoinDataComplete(new JoinData { JoinNumber = 83, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Domain", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("DnsServer")] - public JoinDataComplete DnsServer = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Dns Server", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Dns Server + /// + [JoinName("DnsServer")] + public JoinDataComplete DnsServer = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Dns Server", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("MacAddress")] - public JoinDataComplete MacAddress = new JoinDataComplete(new JoinData { JoinNumber = 85, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Mac Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Mac Address + /// + [JoinName("MacAddress")] + public JoinDataComplete MacAddress = new JoinDataComplete(new JoinData { JoinNumber = 85, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Mac Address", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("DhcpStatus")] - public JoinDataComplete DhcpStatus = new JoinDataComplete(new JoinData { JoinNumber = 86, JoinSpan = 1 }, - new JoinMetadata { Description = "Processor Ethernet Dhcp Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); + /// + /// Processor Ethernet Dhcp Status + /// + [JoinName("DhcpStatus")] + public JoinDataComplete DhcpStatus = new JoinDataComplete(new JoinData { JoinNumber = 86, JoinSpan = 1 }, + new JoinMetadata { Description = "Processor Ethernet Dhcp Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); /// /// Processor Reboot @@ -161,22 +260,23 @@ public class SystemMonitorJoinMap : JoinMapBaseAdvanced new JoinMetadata { Description = "Resets the program", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - /// - /// Constructor to use when instantiating this Join Map without inheriting from it - /// - /// Join this join map will start at - public SystemMonitorJoinMap(uint joinStart) - : this(joinStart, typeof(SystemMonitorJoinMap)) - { - } + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public SystemMonitorJoinMap(uint joinStart) + : this(joinStart, typeof(SystemMonitorJoinMap)) + { + } - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - protected SystemMonitorJoinMap(uint joinStart, Type type) - : base(joinStart, type) - { + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + protected SystemMonitorJoinMap(uint joinStart, Type type) + : base(joinStart, type) + { + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs index 05be3bfa..2a7a46c2 100644 --- a/src/PepperDash.Essentials.Core/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Bridges/JoinMaps/VideoCodecControllerJoinMap.cs @@ -1,7 +1,10 @@ using System; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Bridges.JoinMaps; - +namespace PepperDash.Essentials.Core.Bridges.JoinMaps +{ + /// + /// Represents a VideoCodecControllerJoinMap + /// public class VideoCodecControllerJoinMap : JoinMapBaseAdvanced { #region Digital @@ -23,21 +26,27 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("SendDtmfToSpecificCallIndex")] - public JoinDataComplete SendDtmfToSpecificCallIndex = new JoinDataComplete( - new JoinData - { - JoinNumber = 10, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "If High, will send DTMF tones to the call set by SelectCall analog. If low sends DTMF tones to last connected call.", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// If High, will send DTMF tones to the call set by SelectCall analog. If low sends DTMF tones to last connected call. + /// + [JoinName("SendDtmfToSpecificCallIndex")] + public JoinDataComplete SendDtmfToSpecificCallIndex = new JoinDataComplete( + new JoinData + { + JoinNumber = 10, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "If High, will send DTMF tones to the call set by SelectCall analog. If low sends DTMF tones to last connected call.", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("Dtmf1")] + /// + /// DTMF 1 + /// + [JoinName("Dtmf1")] public JoinDataComplete Dtmf1 = new JoinDataComplete( new JoinData { @@ -51,7 +60,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf2")] + /// + /// DTMF 2 + /// + [JoinName("Dtmf2")] public JoinDataComplete Dtmf2 = new JoinDataComplete( new JoinData { @@ -65,7 +77,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf3")] + /// + /// DTMF 3 + /// + [JoinName("Dtmf3")] public JoinDataComplete Dtmf3 = new JoinDataComplete( new JoinData { @@ -79,7 +94,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf4")] + /// + /// DTMF 4 + /// + [JoinName("Dtmf4")] public JoinDataComplete Dtmf4 = new JoinDataComplete( new JoinData { @@ -93,7 +111,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf5")] + /// + /// DTMF 5 + /// + [JoinName("Dtmf5")] public JoinDataComplete Dtmf5 = new JoinDataComplete( new JoinData { @@ -107,7 +128,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf6")] + /// + /// DTMF 6 + /// + [JoinName("Dtmf6")] public JoinDataComplete Dtmf6 = new JoinDataComplete( new JoinData { @@ -121,7 +145,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf7")] + /// + /// DTMF 7 + /// + [JoinName("Dtmf7")] public JoinDataComplete Dtmf7 = new JoinDataComplete( new JoinData { @@ -135,7 +162,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf8")] + /// + /// DTMF 8 + /// + [JoinName("Dtmf8")] public JoinDataComplete Dtmf8 = new JoinDataComplete( new JoinData { @@ -149,7 +179,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf9")] + /// + /// DTMF 9 + /// + [JoinName("Dtmf9")] public JoinDataComplete Dtmf9 = new JoinDataComplete( new JoinData { @@ -163,7 +196,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("Dtmf0")] + /// + /// DTMF 0 + /// + [JoinName("Dtmf0")] public JoinDataComplete Dtmf0 = new JoinDataComplete( new JoinData { @@ -177,7 +213,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("DtmfStar")] + /// + /// DTMF * + /// + [JoinName("DtmfStar")] public JoinDataComplete DtmfStar = new JoinDataComplete( new JoinData { @@ -191,7 +230,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("DtmfPound")] + /// + /// DTMF # + /// + [JoinName("DtmfPound")] public JoinDataComplete DtmfPound = new JoinDataComplete( new JoinData { @@ -205,7 +247,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("EndAllCalls")] + /// + /// End All Calls + /// + [JoinName("EndAllCalls")] public JoinDataComplete EndAllCalls = new JoinDataComplete( new JoinData { @@ -321,7 +366,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("DialPhone")] + /// + /// Dial Phone + /// + [JoinName("DialPhone")] public JoinDataComplete DialPhone = new JoinDataComplete( new JoinData { @@ -347,12 +395,15 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; }, new JoinMetadata { - Description = "Phone hook state FB", + Description = "Dial Phone", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }); - [JoinName("HangUpPhone")] + /// + /// Hang Up Phone + /// + [JoinName("HangUpPhone")] public JoinDataComplete HangUpPhone = new JoinDataComplete( new JoinData { @@ -366,47 +417,56 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("EndCallStart")] - public JoinDataComplete EndCallStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 81, - JoinSpan = 8 - }, - new JoinMetadata - { - Description = "End a specific call by call index. ", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// End Call + /// + [JoinName("EndCallStart")] + public JoinDataComplete EndCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 81, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "End a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("JoinAllCalls")] - public JoinDataComplete JoinAllCalls = new JoinDataComplete( - new JoinData - { - JoinNumber = 90, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Join all calls", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Join All Calls + /// + [JoinName("JoinAllCalls")] + public JoinDataComplete JoinAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 90, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Join all calls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("JoinCallStart")] - public JoinDataComplete JoinCallStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 91, - JoinSpan = 8 - }, - new JoinMetadata - { - Description = "Join a specific call by call index. ", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Join Call + /// + [JoinName("JoinCallStart")] + public JoinDataComplete JoinCallStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 91, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Join a specific call by call index. ", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); /// /// Directory Search Busy @@ -442,6 +502,22 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); + /// + /// Directory Line Selected + /// + [JoinName("DirectoryLineSelected")] + public JoinDataComplete DirectoryLineSelected = new JoinDataComplete( + new JoinData + { + JoinNumber = 101, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Directory Line Selected FB", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); /// /// Directory Is Root @@ -528,47 +604,56 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("DirectoryDisableAutoDialSelectedLine")] - public JoinDataComplete DirectoryDisableAutoDialSelectedLine = new JoinDataComplete( - new JoinData - { - JoinNumber = 107, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Set high to disable automatic dialing of a contact when selected", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Directory Disable Auto Dial Selected Line + /// + [JoinName("DirectoryDisableAutoDialSelectedLine")] + public JoinDataComplete DirectoryDisableAutoDialSelectedLine = new JoinDataComplete( + new JoinData + { + JoinNumber = 107, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Set high to disable automatic dialing of a contact when selected", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("DirectoryDialSelectedContactMethod")] - public JoinDataComplete DirectoryDialSelectedContactMethod = new JoinDataComplete( - new JoinData - { - JoinNumber = 108, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Pulse to dial the selected contact method", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Directory Dial Selected Contact Method + /// + [JoinName("DirectoryDialSelectedContactMethod")] + public JoinDataComplete DirectoryDialSelectedContactMethod = new JoinDataComplete( + new JoinData + { + JoinNumber = 108, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to dial the selected contact method", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("DirectoryClearSelected")] - public JoinDataComplete DirectoryClearSelected = new JoinDataComplete( - new JoinData - { - JoinNumber = 110, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Clear Selected Entry and String from Search", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Directory Clear Selected + /// + [JoinName("DirectoryClearSelected")] + public JoinDataComplete DirectoryClearSelected = new JoinDataComplete( + new JoinData + { + JoinNumber = 110, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Clear Selected Entry and String from Search", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); /// /// Camera Tilt Up @@ -672,47 +757,56 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("CameraFocusNear")] - public JoinDataComplete CameraFocusNear = new JoinDataComplete( - new JoinData - { - JoinNumber = 117, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Camera Focus Near", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Camera Focus Near + /// + [JoinName("CameraFocusNear")] + public JoinDataComplete CameraFocusNear = new JoinDataComplete( + new JoinData + { + JoinNumber = 117, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Focus Near", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("CameraFocusFar")] - public JoinDataComplete CameraFocusFar = new JoinDataComplete( - new JoinData - { - JoinNumber = 118, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Camera Focus Far", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Camera Focus Far + /// + [JoinName("CameraFocusFar")] + public JoinDataComplete CameraFocusFar = new JoinDataComplete( + new JoinData + { + JoinNumber = 118, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Focus Far", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("CameraFocusAuto")] - public JoinDataComplete CameraFocusAuto = new JoinDataComplete( - new JoinData - { - JoinNumber = 119, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Camera Auto Focus Trigger", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Camera Auto Focus + /// + [JoinName("CameraFocusAuto")] + public JoinDataComplete CameraFocusAuto = new JoinDataComplete( + new JoinData + { + JoinNumber = 119, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Camera Auto Focus Trigger", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); /// /// Camera Preset Save @@ -867,7 +961,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("DialMeetingStart")] + /// + /// Dial Meeting Start + /// + [JoinName("DialMeetingStart")] public JoinDataComplete DialMeetingStart = new JoinDataComplete( new JoinData { @@ -1017,33 +1114,39 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("RemoveSelectedRecentCallItem")] - public JoinDataComplete RemoveSelectedRecentCallItem = new JoinDataComplete( - new JoinData - { - JoinNumber = 181, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Pulse to remove the selected recent call item specified by the SelectRecentCallItem analog join", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Remove Selected Recent Call Item + /// + [JoinName("RemoveSelectedRecentCallItem")] + public JoinDataComplete RemoveSelectedRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to remove the selected recent call item specified by the SelectRecentCallItem analog join", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("DialSelectedRecentCallItem")] - public JoinDataComplete DialSelectedRecentCallItem = new JoinDataComplete( - new JoinData - { - JoinNumber = 182, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Pulse to dial the selected recent call item specified by the SelectRecentCallItem analog join", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Dial Selected Recent Call Item + /// + [JoinName("DialSelectedRecentCallItem")] + public JoinDataComplete DialSelectedRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 182, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Pulse to dial the selected recent call item specified by the SelectRecentCallItem analog join", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); /// /// Source Share Start @@ -1130,117 +1233,141 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Digital }); - [JoinName("HoldAllCalls")] - public JoinDataComplete HoldAllCalls = new JoinDataComplete( - new JoinData - { - JoinNumber = 220, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Holds all calls", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Hold All Calls + /// + [JoinName("HoldAllCalls")] + public JoinDataComplete HoldAllCalls = new JoinDataComplete( + new JoinData + { + JoinNumber = 220, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Holds all calls", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("HoldCallsStart")] - public JoinDataComplete HoldCallsStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 221, - JoinSpan = 8 - }, - new JoinMetadata - { - Description = "Holds Call at specified index. FB reported on Call Status XSIG", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Hold Call at Index + /// + [JoinName("HoldCallsStart")] + public JoinDataComplete HoldCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 221, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Holds Call at specified index. FB reported on Call Status XSIG", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("ResumeCallsStart")] - public JoinDataComplete ResumeCallsStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 231, - JoinSpan = 8 - }, - new JoinMetadata - { - Description = "Resume Call at specified index", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Resume All Calls + /// + [JoinName("ResumeCallsStart")] + public JoinDataComplete ResumeCallsStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 231, + JoinSpan = 8 + }, + new JoinMetadata + { + Description = "Resume Call at specified index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("MultiSiteOptionIsEnabled")] - public JoinDataComplete MultiSiteOptionIsEnabled = new JoinDataComplete( - new JoinData - { - JoinNumber = 301, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Multi site option is enabled FB", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Multi Site Option Is Enabled + /// + [JoinName("MultiSiteOptionIsEnabled")] + public JoinDataComplete MultiSiteOptionIsEnabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 301, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Multi site option is enabled FB", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("AutoAnswerEnabled")] - public JoinDataComplete AutoAnswerEnabled = new JoinDataComplete( - new JoinData - { - JoinNumber = 302, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Auto Answer is enabled FB", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Digital - }); + /// + /// Auto Answer Enabled + /// + [JoinName("AutoAnswerEnabled")] + public JoinDataComplete AutoAnswerEnabled = new JoinDataComplete( + new JoinData + { + JoinNumber = 302, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Auto Answer is enabled FB", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("ParticipantAudioMuteToggleStart")] - public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete( + /// + /// Participant Audio Mute Toggle + /// + [JoinName("ParticipantAudioMuteToggleStart")] + public JoinDataComplete ParticipantAudioMuteToggleStart = new JoinDataComplete( new JoinData { JoinNumber = 501, JoinSpan = 50 }, - new JoinMetadata - { - Description = "Toggles the participant's audio mute status", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + new JoinMetadata + { + Description = "Toggles the participant's audio mute status", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("ParticipantVideoMuteToggleStart")] - public JoinDataComplete ParticipantVideoMuteToggleStart = new JoinDataComplete( + /// + /// Participant Video Mute Toggle + /// + [JoinName("ParticipantVideoMuteToggleStart")] + public JoinDataComplete ParticipantVideoMuteToggleStart = new JoinDataComplete( new JoinData { JoinNumber = 801, JoinSpan = 50 }, - new JoinMetadata - { - Description = "Toggles the participant's video mute status", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + new JoinMetadata + { + Description = "Toggles the participant's video mute status", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); - [JoinName("ParticipantPinToggleStart")] - public JoinDataComplete ParticipantPinToggleStart = new JoinDataComplete( + /// + /// Participant Pin Toggle + /// + [JoinName("ParticipantPinToggleStart")] + public JoinDataComplete ParticipantPinToggleStart = new JoinDataComplete( new JoinData { JoinNumber = 1101, JoinSpan = 50 }, - new JoinMetadata - { - Description = "Toggles the participant's pin status", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Digital - }); + new JoinMetadata + { + Description = "Toggles the participant's pin status", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Digital + }); #endregion @@ -1249,49 +1376,58 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; #region Analog - // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set - [JoinName("MeetingsToDisplay")] - public JoinDataComplete MeetingsToDisplay = new JoinDataComplete( - new JoinData - { - JoinNumber = 40, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Set/FB the number of meetings to display via the bridge xsig; default: 3 meetings.", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Analog - }); + // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set - [JoinName("SelectCall")] - public JoinDataComplete SelectCall = new JoinDataComplete( - new JoinData - { - JoinNumber = 24, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Sets the selected Call for DTMF commands. Valid values 1-8", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Meetings To Display + /// + [JoinName("MeetingsToDisplay")] + public JoinDataComplete MeetingsToDisplay = new JoinDataComplete( + new JoinData + { + JoinNumber = 40, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Set/FB the number of meetings to display via the bridge xsig; default: 3 meetings.", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); + /// + /// Select Call + /// + [JoinName("SelectCall")] + public JoinDataComplete SelectCall = new JoinDataComplete( + new JoinData + { + JoinNumber = 24, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Sets the selected Call for DTMF commands. Valid values 1-8", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); - [JoinName("ConnectedCallCount")] - public JoinDataComplete ConnectedCallCount = new JoinDataComplete( - new JoinData - { - JoinNumber = 25, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Reports the number of currently connected calls", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Connected Call Count + /// + [JoinName("ConnectedCallCount")] + public JoinDataComplete ConnectedCallCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 25, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of currently connected calls", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); /// /// Minutes Before Meeting Start @@ -1327,19 +1463,22 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Analog }); - [JoinName("CameraCount")] - public JoinDataComplete CameraCount = new JoinDataComplete( - new JoinData - { - JoinNumber = 61, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Reports the number of cameras", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Camera Count + /// + [JoinName("CameraCount")] + public JoinDataComplete CameraCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 61, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of cameras", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); /// /// Directory Row Count @@ -1370,53 +1509,61 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; }, new JoinMetadata { - Description = "Directory Select Row", + Description = "Directory Select Row and Feedback", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Analog }); + /// + /// Selected Contact Method Count + /// + [JoinName("SelectedContactMethodCount")] + public JoinDataComplete SelectedContactMethodCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 102, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Reports the number of contact methods for the selected contact", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); - [JoinName("SelectedContactMethodCount")] - public JoinDataComplete SelectedContactMethodCount = new JoinDataComplete( - new JoinData - { - JoinNumber = 102, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Reports the number of contact methods for the selected contact", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Select Contact Method + /// + [JoinName("SelectContactMethod")] + public JoinDataComplete SelectContactMethod = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selects a contact method by index", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Analog + }); - [JoinName("SelectContactMethod")] - public JoinDataComplete SelectContactMethod = new JoinDataComplete( - new JoinData - { - JoinNumber = 103, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Selects a contact method by index", - JoinCapabilities = eJoinCapabilities.FromSIMPL, - JoinType = eJoinType.Analog - }); - - [JoinName("DirectorySelectRowFeedback")] - public JoinDataComplete DirectorySelectRowFeedback = new JoinDataComplete( - new JoinData - { - JoinNumber = 104, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Directory Selected Row FB", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Directory Select Row Feedback + /// + [JoinName("DirectorySelectRowFeedback")] + public JoinDataComplete DirectorySelectRowFeedback = new JoinDataComplete( + new JoinData + { + JoinNumber = 104, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Directory Select Row and Feedback", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); /// @@ -1432,23 +1579,26 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; new JoinMetadata { Description = "Camera Preset Select", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog }); - [JoinName("FarEndPresetSelect")] - public JoinDataComplete FarEndPresetSelect = new JoinDataComplete( - new JoinData - { - JoinNumber = 122, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Far End Preset Preset Select", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Far End Preset Select + /// + [JoinName("FarEndPresetSelect")] + public JoinDataComplete FarEndPresetSelect = new JoinDataComplete( + new JoinData + { + JoinNumber = 122, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Far End Preset Preset Select", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); /// /// Participant Count @@ -1501,47 +1651,56 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Analog }); - [JoinName("SelectRecentCallItem")] - public JoinDataComplete SelectRecentCallItem = new JoinDataComplete( - new JoinData - { - JoinNumber = 180, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Select/FB for Recent Call Item. Valid values 1 - 10", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Select Recent Call Item + /// + [JoinName("SelectRecentCallItem")] + public JoinDataComplete SelectRecentCallItem = new JoinDataComplete( + new JoinData + { + JoinNumber = 180, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Select/FB for Recent Call Item. Valid values 1 - 10", + JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinType = eJoinType.Analog + }); - [JoinName("RecentCallOccurrenceType")] - public JoinDataComplete RecentCallOccurrenceType = new JoinDataComplete( - new JoinData - { - JoinNumber = 181, - JoinSpan = 10 - }, - new JoinMetadata - { - Description = "Recent Call Occurrence Type. [0-3] 0 = Unknown, 1 = Placed, 2 = Received, 3 = NoAnswer", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Recent Call Occurrence Type + /// + [JoinName("RecentCallOccurrenceType")] + public JoinDataComplete RecentCallOccurrenceType = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Call Occurrence Type. [0-3] 0 = Unknown, 1 = Placed, 2 = Received, 3 = NoAnswer", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); - [JoinName("RecentCallCount")] - public JoinDataComplete RecentCallCount = new JoinDataComplete( - new JoinData - { - JoinNumber = 191, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Recent Call Count", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Analog - }); + /// + /// Recent Call Count + /// + [JoinName("RecentCallCount")] + public JoinDataComplete RecentCallCount = new JoinDataComplete( + new JoinData + { + JoinNumber = 191, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Recent Call Count", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Analog + }); #endregion @@ -1562,11 +1721,14 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; new JoinMetadata { Description = "Value to dial when ManualDial digital join is pulsed", - JoinCapabilities = eJoinCapabilities.ToFromSIMPL, + JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("PhoneDialString")] + /// + /// Phone Dial String + /// + [JoinName("PhoneDialString")] public JoinDataComplete PhoneDialString = new JoinDataComplete( new JoinData { @@ -1580,7 +1742,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Serial }); - [JoinName("CurrentCallData")] + /// + /// Current Call Data + /// + [JoinName("CurrentCallData")] public JoinDataComplete CurrentCallData = new JoinDataComplete( new JoinData { @@ -1696,19 +1861,22 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Serial }); - [JoinName("ContactMethods")] - public JoinDataComplete ContactMethods = new JoinDataComplete( - new JoinData - { - JoinNumber = 103, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Contact Methods - XSig, 10 entries", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Contact Methods + /// + [JoinName("ContactMethods")] + public JoinDataComplete ContactMethods = new JoinDataComplete( + new JoinData + { + JoinNumber = 103, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Contact Methods - XSig, 10 entries", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); /// /// Camera Preset Names @@ -1727,7 +1895,10 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Serial }); - [JoinName("CurrentLayoutStringFb")] + /// + /// Current Layout String + /// + [JoinName("CurrentLayoutStringFb")] public JoinDataComplete CurrentLayoutStringFb = new JoinDataComplete( new JoinData { @@ -1741,6 +1912,41 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Serial }); + /// + /// Available Layouts XSig + /// + [JoinName("AvailableLayoutsFb")] + public JoinDataComplete AvailableLayoutsFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "xSig of all available layouts", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); + + /// + /// Select Layout + /// + [JoinName("SelectLayout")] + public JoinDataComplete SelectLayout = new JoinDataComplete( + new JoinData + { + JoinNumber = 142, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Select Layout by string", + JoinCapabilities = eJoinCapabilities.FromSIMPL, + JoinType = eJoinType.Serial + }); + + /// /// Current Participants XSig /// @@ -1758,75 +1964,90 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; JoinType = eJoinType.Serial }); - [JoinName("CameraNamesFb")] - public JoinDataComplete CameraNamesFb = new JoinDataComplete( - new JoinData - { - JoinNumber = 161, - JoinSpan = 10 - }, - new JoinMetadata - { - Description = "Camera Name Fb", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Camera Names XSig + /// + [JoinName("CameraNamesFb")] + public JoinDataComplete CameraNamesFb = new JoinDataComplete( + new JoinData + { + JoinNumber = 161, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Camera Name Fb", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("SelectedRecentCallName")] - public JoinDataComplete SelectedRecentCallName = new JoinDataComplete( - new JoinData - { - JoinNumber = 171, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Selected Recent Call Name", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Selected Recent Call Name + /// + [JoinName("SelectedRecentCallName")] + public JoinDataComplete SelectedRecentCallName = new JoinDataComplete( + new JoinData + { + JoinNumber = 171, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selected Recent Call Name", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("SelectedRecentCallNumber")] - public JoinDataComplete SelectedRecentCallNumber = new JoinDataComplete( - new JoinData - { - JoinNumber = 172, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "Selected Recent Call Number", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Selected Recent Call Number + /// + [JoinName("SelectedRecentCallNumber")] + public JoinDataComplete SelectedRecentCallNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 172, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "Selected Recent Call Number", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("RecentCallNamesStart")] - public JoinDataComplete RecentCallNamesStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 181, - JoinSpan = 10 - }, - new JoinMetadata - { - Description = "Recent Call Names", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Recent Call Names + /// + [JoinName("RecentCallNamesStart")] + public JoinDataComplete RecentCallNamesStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 181, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Call Names", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("RecentCallTimesStart")] - public JoinDataComplete RecentCallTimesStart = new JoinDataComplete( - new JoinData - { - JoinNumber = 191, - JoinSpan = 10 - }, - new JoinMetadata - { - Description = "Recent Calls Times", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Recent Call Numbers + /// + [JoinName("RecentCallTimesStart")] + public JoinDataComplete RecentCallTimesStart = new JoinDataComplete( + new JoinData + { + JoinNumber = 191, + JoinSpan = 10 + }, + new JoinMetadata + { + Description = "Recent Calls Times", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); /// /// Current Source @@ -1857,80 +2078,95 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; }, new JoinMetadata { - Description = "Current selfview PIP position string FB", + Description = "advance selfview position", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial }); - [JoinName("DeviceIpAddresss")] - public JoinDataComplete DeviceIpAddresss = new JoinDataComplete( - new JoinData - { - JoinNumber = 301, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "IP Address of device", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// Device IP Address + /// + [JoinName("DeviceIpAddresss")] + public JoinDataComplete DeviceIpAddresss = new JoinDataComplete( + new JoinData + { + JoinNumber = 301, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "IP Address of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("SipPhoneNumber")] - public JoinDataComplete SipPhoneNumber = new JoinDataComplete( - new JoinData - { - JoinNumber = 302, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "SIP phone number of device", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// SIP Phone Number + /// + [JoinName("SipPhoneNumber")] + public JoinDataComplete SipPhoneNumber = new JoinDataComplete( + new JoinData + { + JoinNumber = 302, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SIP phone number of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("E164Alias")] - public JoinDataComplete E164Alias = new JoinDataComplete( - new JoinData - { - JoinNumber = 303, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "E164 alias of device", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// E164 Alias + /// + [JoinName("E164Alias")] + public JoinDataComplete E164Alias = new JoinDataComplete( + new JoinData + { + JoinNumber = 303, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "E164 alias of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("H323Id")] - public JoinDataComplete H323Id = new JoinDataComplete( - new JoinData - { - JoinNumber = 304, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "H323 ID of device", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// H323 ID + /// + [JoinName("H323Id")] + public JoinDataComplete H323Id = new JoinDataComplete( + new JoinData + { + JoinNumber = 304, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "H323 ID of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); - [JoinName("SipUri")] - public JoinDataComplete SipUri = new JoinDataComplete( - new JoinData - { - JoinNumber = 305, - JoinSpan = 1 - }, - new JoinMetadata - { - Description = "SIP URI of device", - JoinCapabilities = eJoinCapabilities.ToSIMPL, - JoinType = eJoinType.Serial - }); + /// + /// SIP URI + /// + [JoinName("SipUri")] + public JoinDataComplete SipUri = new JoinDataComplete( + new JoinData + { + JoinNumber = 305, + JoinSpan = 1 + }, + new JoinMetadata + { + Description = "SIP URI of device", + JoinCapabilities = eJoinCapabilities.ToSIMPL, + JoinType = eJoinType.Serial + }); /// /// Selected Directory Entry Name @@ -2005,3 +2241,4 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps; { } } +} diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs index 0c053d6b..a5aa0d13 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs @@ -10,116 +10,182 @@ using Crestron.SimplSharpPro.DM; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - - public class CecPortController : Device, IBasicCommunicationWithStreamDebugging +namespace PepperDash.Essentials.Core { - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - public event EventHandler BytesReceived; - public event EventHandler TextReceived; - - public bool IsConnected { get { return true; } } - - ICec Port; - - public CecPortController(string key, Func postActivationFunc, - EssentialsControlPropertiesConfig config):base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - - AddPostActivationAction(() => - { - Port = postActivationFunc(config); - - Port.StreamCec.CecChange += StreamCec_CecChange; - }); - } - - public CecPortController(string key, ICec port) - : base(key) - { - Port = port; - - Port.StreamCec.CecChange += new CecChangeEventHandler(StreamCec_CecChange); - } - - void StreamCec_CecChange(Cec cecDevice, CecEventArgs args) - { - if (args.EventId == CecEventIds.CecMessageReceivedEventId) - OnDataReceived(cecDevice.Received.StringValue); - else if (args.EventId == CecEventIds.ErrorFeedbackEventId) - if(cecDevice.ErrorFeedback.BoolValue) - Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error"); - } - - void OnDataReceived(string s) - { - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(s); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); - textHandler(this, new GenericCommMethodReceiveTextArgs(s)); - } - } - - #region IBasicCommunication Members - - public void SendText(string text) - { - if (Port == null) - return; - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); - Port.StreamCec.Send.StringValue = text; - } - - public void SendBytes(byte[] bytes) - { - if (Port == null) - return; - var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - Port.StreamCec.Send.StringValue = text; - } - - public void Connect() - { - } - - public void Disconnect() - { - } - - #endregion - /// - /// + /// Represents a CecPortController /// - /// - public void SimulateReceive(string s) + public class CecPortController : Device, IBasicCommunicationWithStreamDebugging { - // split out hex chars and build string - var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); - StringBuilder b = new StringBuilder(); - foreach (var t in split) + /// + /// Gets or sets the StreamDebugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Event raised when bytes are received + /// + public event EventHandler BytesReceived; + + /// + /// Event raised when text is received + /// + public event EventHandler TextReceived; + + /// + /// Gets or sets the IsConnected + /// + public bool IsConnected { get { return true; } } + + ICec Port; + + bool _cecSubscribed; + + /// + /// Constructor + /// + /// key of the device + /// post activation function for the device + /// configuration for the device + public CecPortController(string key, Func postActivationFunc, + EssentialsControlPropertiesConfig config) : base(key) { - if (t.StartsWith(@"\") && t.Length == 4) - b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); - else - b.Append(t); + StreamDebugging = new CommunicationStreamDebugging(key); + + AddPostActivationAction(() => + { + Port = postActivationFunc(config); + + TryEnsureCecSubscription(); + }); } - OnDataReceived(b.ToString()); + /// + /// Constructor + /// + /// key of the device + /// CEC port + public CecPortController(string key, ICec port) + : base(key) + { + Port = port; + + TryEnsureCecSubscription(); + } + + /// + /// Subscribes to the CEC change event once is available. + /// Safe to call repeatedly; the subscription is only wired a single time. + /// If StreamCec is null during construction, this is retried when send methods invoke + /// this method later. + /// + void TryEnsureCecSubscription() + { + if (_cecSubscribed) + return; + + if (Port?.StreamCec == null) + { + Debug.LogMessage(LogEventLevel.Warning, this, "StreamCec is not available; CEC feedback is deferred until the device is ready"); + return; + } + + Port.StreamCec.CecChange += new CecChangeEventHandler(StreamCec_CecChange); + _cecSubscribed = true; + Debug.LogMessage(LogEventLevel.Information, this, "Subscribed to CEC feedback"); + } + + void StreamCec_CecChange(Cec cecDevice, CecEventArgs args) + { + if (args.EventId == CecEventIds.CecMessageReceivedEventId) + OnDataReceived(cecDevice.Received.StringValue); + else if (args.EventId == CecEventIds.ErrorFeedbackEventId) + if (cecDevice.ErrorFeedback.BoolValue) + Debug.LogMessage(LogEventLevel.Verbose, this, "CEC NAK Error"); + } + + void OnDataReceived(string s) + { + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(s); + this.PrintReceivedBytes(bytes); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + this.PrintReceivedText(s); + textHandler(this, new GenericCommMethodReceiveTextArgs(s)); + } + } + + #region IBasicCommunication Members + + /// + /// SendText method + /// + public void SendText(string text) + { + if (Port?.StreamCec == null) + return; + TryEnsureCecSubscription(); + this.PrintSentText(text); + Port.StreamCec.Send.StringValue = text; + } + + /// + /// SendBytes method + /// + public void SendBytes(byte[] bytes) + { + if (Port?.StreamCec == null) + return; + TryEnsureCecSubscription(); + var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + this.PrintSentBytes(bytes); + Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + Port.StreamCec.Send.StringValue = text; + } + + /// + /// Connect method + /// + public void Connect() + { + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + } + + #endregion + + /// + /// + /// + /// + /// + /// SimulateReceive method + /// + public void SimulateReceive(string s) + { + // split out hex chars and build string + var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); + StringBuilder b = new StringBuilder(); + foreach (var t in split) + { + if (t.StartsWith(@"\") && t.Length == 4) + b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); + else + b.Append(t); + } + + OnDataReceived(b.ToString()); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs index a4407e01..ec679fae 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs @@ -9,11 +9,17 @@ using PepperDash.Core.Logging; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ComPortController + /// public class ComPortController : Device, IBasicCommunicationWithStreamDebugging { - public CommunicationStreamDebugging StreamDebugging { get; private set; } + /// + /// Gets or sets the StreamDebugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } /// /// Event fired when bytes are received @@ -33,20 +39,27 @@ namespace PepperDash.Essentials.Core; ComPort Port; ComPort.ComPortSpec Spec; - public ComPortController(string key, Func postActivationFunc, - ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); + /// + /// Constructor + /// + /// + /// + /// + /// + public ComPortController(string key, Func postActivationFunc, + ComPort.ComPortSpec spec, EssentialsControlPropertiesConfig config) : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); Spec = spec; - AddPostActivationAction(() => - { - Port = postActivationFunc(config); + AddPostActivationAction(() => + { + Port = postActivationFunc(config); - RegisterAndConfigureComPort(); - }); - } + RegisterAndConfigureComPort(); + }); + } /// /// Constructor @@ -70,22 +83,13 @@ namespace PepperDash.Essentials.Core; RegisterAndConfigureComPort(); } - private void RegisterAndConfigureComPort() - { - if (Port == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Configured com Port for this device does not exist."); - return; - } - if (Port.Parent is CrestronControlSystem) - { - var result = Port.Register(); - if (result != eDeviceRegistrationUnRegistrationResponse.Success) - { - Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Com port: {0}", result); - return; // false - } - } + private void RegisterAndConfigureComPort() + { + if (Port == null) + { + this.LogInformation($"Configured {Port.Parent.GetType().Name}-comport-{Port.ID} for {Key} does not exist."); + return; + } if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102) @@ -120,33 +124,31 @@ namespace PepperDash.Essentials.Core; void Port_SerialDataReceived(ComPort ReceivingComPort, ComPortSerialDataEventArgs args) { - OnDataReceived(args.SerialData); + OnDataReceived(args.SerialData); } - void OnDataReceived(string s) - { + void OnDataReceived(string s) + { var eventSubscribed = false; - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(s); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes)); - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(s); + this.PrintReceivedBytes(bytes); + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); eventSubscribed = true; - } - var textHandler = TextReceived; - if (textHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s); - textHandler(this, new GenericCommMethodReceiveTextArgs(s)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + this.PrintReceivedText(s); + textHandler(this, new GenericCommMethodReceiveTextArgs(s)); eventSubscribed = true; - } + } - if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered"); - } + if (!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered"); + } /// /// Deactivate method @@ -167,9 +169,8 @@ namespace PepperDash.Essentials.Core; if (Port == null) return; - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} characters of text: '{1}'", text.Length, text); - Port.Send(text); + this.PrintSentText(text); + Port.Send(text); } /// @@ -180,8 +181,7 @@ namespace PepperDash.Essentials.Core; if (Port == null) return; var text = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.LogMessage(LogEventLevel.Information, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + this.PrintSentBytes(bytes); Port.Send(text); } @@ -202,23 +202,27 @@ namespace PepperDash.Essentials.Core; #endregion - /// - /// - /// - /// - public void SimulateReceive(string s) - { - // split out hex chars and build string - var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); - StringBuilder b = new StringBuilder(); - foreach (var t in split) - { - if (t.StartsWith(@"\") && t.Length == 4) - b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); - else - b.Append(t); - } + /// + /// + /// + /// + /// + /// SimulateReceive method + /// + public void SimulateReceive(string s) + { + // split out hex chars and build string + var split = Regex.Split(s, @"(\\[Xx][0-9a-fA-F][0-9a-fA-F])"); + StringBuilder b = new StringBuilder(); + foreach (var t in split) + { + if (t.StartsWith(@"\") && t.Length == 4) + b.Append((char)(Convert.ToByte(t.Substring(2, 2), 16))); + else + b.Append(t); + } - OnDataReceived(b.ToString()); - } + OnDataReceived(b.ToString()); + } } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs b/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs index 6ef2f916..11e2bc74 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ComSpecJsonConverter.cs @@ -13,89 +13,119 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// This converter creates a proper ComPort.ComPortSpec struct from more-friendly JSON values. It uses -/// ComSpecPropsJsonConverter to finish the individual properties. -/// -public class ComSpecJsonConverter : JsonConverter +namespace PepperDash.Essentials.Core { - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + /// + /// This converter creates a proper ComPort.ComPortSpec struct from more-friendly JSON values. It uses + /// ComSpecPropsJsonConverter to finish the individual properties. + /// + public class ComSpecJsonConverter : JsonConverter { - if (objectType == typeof(ComPort.ComPortSpec?)) + /// + /// ReadJson method + /// + /// reader to use + /// type of the object being read + /// existing value of the object being read + /// serializer to use + /// deserialized object + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - var newSer = new JsonSerializer(); - newSer.Converters.Add(new ComSpecPropsJsonConverter()); - newSer.ObjectCreationHandling = ObjectCreationHandling.Replace; - return newSer.Deserialize(reader); + if (objectType == typeof(ComPort.ComPortSpec?)) + { + var newSer = new JsonSerializer(); + newSer.Converters.Add(new ComSpecPropsJsonConverter()); + newSer.ObjectCreationHandling = ObjectCreationHandling.Replace; + return newSer.Deserialize(reader); + } + return null; + } + + /// + /// CanConvert method + /// + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ComPort.ComPortSpec?); + } + + /// + /// Gets or sets the CanRead + /// + public override bool CanRead { get { return true; } } + + /// + /// Gets or sets the CanWrite + /// + /// + public override bool CanWrite { get { return false; } } + + /// + /// WriteJson method + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); } - return null; } /// - /// + /// Represents a ComSpecPropsJsonConverter /// - public override bool CanConvert(Type objectType) + public class ComSpecPropsJsonConverter : JsonConverter { - return objectType == typeof(ComPort.ComPortSpec?); + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ComPort.eComBaudRates) + || objectType == typeof(ComPort.eComDataBits) + || objectType == typeof(ComPort.eComParityType) + || objectType == typeof(ComPort.eComHardwareHandshakeType) + || objectType == typeof(ComPort.eComSoftwareHandshakeType) + || objectType == typeof(ComPort.eComProtocolType) + || objectType == typeof(ComPort.eComStopBits); + } + + /// + /// Gets or sets the CanRead + /// + /// + public override bool CanRead { get { return true; } } + + /// + /// ReadJson method + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + //Debug.LogMessage(LogEventLevel.Verbose, "ReadJson type: " + objectType.Name); + if (objectType == typeof(ComPort.eComBaudRates)) + return Enum.Parse(typeof(ComPort.eComBaudRates), "ComspecBaudRate" + reader.Value, false); + else if (objectType == typeof(ComPort.eComDataBits)) + return Enum.Parse(typeof(ComPort.eComDataBits), "ComspecDataBits" + reader.Value, true); + else if (objectType == typeof(ComPort.eComHardwareHandshakeType)) + return Enum.Parse(typeof(ComPort.eComHardwareHandshakeType), "ComspecHardwareHandshake" + reader.Value, true); + else if (objectType == typeof(ComPort.eComParityType)) + return Enum.Parse(typeof(ComPort.eComParityType), "ComspecParity" + reader.Value, true); + else if (objectType == typeof(ComPort.eComProtocolType)) + return Enum.Parse(typeof(ComPort.eComProtocolType), "ComspecProtocol" + reader.Value, true); + else if (objectType == typeof(ComPort.eComSoftwareHandshakeType)) + return Enum.Parse(typeof(ComPort.eComSoftwareHandshakeType), "ComspecSoftwareHandshake" + reader.Value, true); + else if (objectType == typeof(ComPort.eComStopBits)) + return Enum.Parse(typeof(ComPort.eComStopBits), "ComspecStopBits" + reader.Value, true); + return null; + } + + /// + /// WriteJson method + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } } - public override bool CanRead { get { return true; } } - /// - /// This converter will not be used for writing - /// - public override bool CanWrite { get { return false; } } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } -} - -/// -/// The gist of this converter: The comspec JSON comes in with normal values that need to be converted -/// into enum names. This converter takes the value and applies the appropriate enum's name prefix to the value -/// and then returns the enum value using Enum.Parse. NOTE: Does not write -/// -public class ComSpecPropsJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ComPort.eComBaudRates) - || objectType == typeof(ComPort.eComDataBits) - || objectType == typeof(ComPort.eComParityType) - || objectType == typeof(ComPort.eComHardwareHandshakeType) - || objectType == typeof(ComPort.eComSoftwareHandshakeType) - || objectType == typeof(ComPort.eComProtocolType) - || objectType == typeof(ComPort.eComStopBits); - } - - public override bool CanRead { get { return true; } } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - //Debug.LogMessage(LogEventLevel.Verbose, "ReadJson type: " + objectType.Name); - if (objectType == typeof(ComPort.eComBaudRates)) - return Enum.Parse(typeof(ComPort.eComBaudRates), "ComspecBaudRate" + reader.Value, false); - else if (objectType == typeof(ComPort.eComDataBits)) - return Enum.Parse(typeof(ComPort.eComDataBits), "ComspecDataBits" + reader.Value, true); - else if (objectType == typeof(ComPort.eComHardwareHandshakeType)) - return Enum.Parse(typeof(ComPort.eComHardwareHandshakeType), "ComspecHardwareHandshake" + reader.Value, true); - else if (objectType == typeof(ComPort.eComParityType)) - return Enum.Parse(typeof(ComPort.eComParityType), "ComspecParity" + reader.Value, true); - else if (objectType == typeof(ComPort.eComProtocolType)) - return Enum.Parse(typeof(ComPort.eComProtocolType), "ComspecProtocol" + reader.Value, true); - else if (objectType == typeof(ComPort.eComSoftwareHandshakeType)) - return Enum.Parse(typeof(ComPort.eComSoftwareHandshakeType), "ComspecSoftwareHandshake" + reader.Value, true); - else if (objectType == typeof(ComPort.eComStopBits)) - return Enum.Parse(typeof(ComPort.eComStopBits), "ComspecStopBits" + reader.Value, true); - return null; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs index 3ba5300c..b28331a0 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs @@ -8,8 +8,8 @@ using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -59,36 +59,54 @@ namespace PepperDash.Essentials.Core; case eControlMethod.Com: comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig); break; - case eControlMethod.Cec: - comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig); - break; + case eControlMethod.ComBridge: + comm = new CommBridge(deviceConfig.Key + "-simpl", deviceConfig.Name + " Simpl"); + break; + case eControlMethod.Cec: + comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig); + break; case eControlMethod.IR: break; - case eControlMethod.Ssh: - { - var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password); - ssh.AutoReconnect = c.AutoReconnect; - ssh.DisableEcho = c.DisableSshEcho; - if(ssh.AutoReconnect) - ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; - comm = ssh; - break; - } - case eControlMethod.Tcpip: - { - var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize); - tcp.AutoReconnect = c.AutoReconnect; - if (tcp.AutoReconnect) - tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; - comm = tcp; - break; - } - case eControlMethod.Udp: - { - var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize); - comm = udp; - break; - } + case eControlMethod.Ssh: + { + var ssh = new GenericSshClient(deviceConfig.Key + "-ssh", c.Address, c.Port, c.Username, c.Password) + { + AutoReconnect = c.AutoReconnect, + DisableEcho = c.DisableSshEcho + }; + if (ssh.AutoReconnect) + ssh.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; + comm = ssh; + break; + } + case eControlMethod.Tcpip: + { + var tcp = new GenericTcpIpClient(deviceConfig.Key + "-tcp", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; + if (tcp.AutoReconnect) + tcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; + comm = tcp; + break; + } + case eControlMethod.Udp: + { + var udp = new GenericUdpServer(deviceConfig.Key + "-udp", c.Address, c.Port, c.BufferSize); + comm = udp; + break; + } + case eControlMethod.UdpClient: + { + var udpClient = new GenericUdpClient(deviceConfig.Key + "-udpClient", c.Address, c.Port, c.BufferSize) + { + AutoReconnect = c.AutoReconnect + }; + if (udpClient.AutoReconnect) + udpClient.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs; + comm = udpClient; + break; + } case eControlMethod.Telnet: break; case eControlMethod.SecureTcpIp: @@ -131,16 +149,19 @@ namespace PepperDash.Essentials.Core; return null; } - /// - /// Gets an ICec port from a RoutingInput or RoutingOutput on a device - /// - /// - /// - public static ICec GetCecPort(ControlPropertiesConfig config) - { - try - { - var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey); + /// + /// Gets an ICec port from a RoutingInput or RoutingOutput on a device + /// + /// + /// + /// + /// GetCecPort method + /// + public static ICec GetCecPort(ControlPropertiesConfig config) + { + try + { + var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey); Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null ? "is not valid, failed to get cec port" @@ -156,10 +177,10 @@ namespace PepperDash.Essentials.Core; } - var inputsOutputs = dev as IRoutingMidpoint; + var inputsOutputs = dev as IRoutingInputsOutputs; if (inputsOutputs == null) { - Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingMidpoint, failed to get CEC port called '{1}'", + Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'", config.ControlPortDevKey, config.ControlPortName); return null; @@ -186,7 +207,7 @@ namespace PepperDash.Essentials.Core; config.ControlPortDevKey, config.ControlPortName); return null; - } + } /// /// Helper to grab the IComPorts device for this PortDeviceKey. Key "controlSystem" will @@ -212,65 +233,86 @@ namespace PepperDash.Essentials.Core; } } -/// -/// -/// -public class EssentialsControlPropertiesConfig : - ControlPropertiesConfig -{ + /// + /// Represents a EssentialsControlPropertiesConfig + /// + public class EssentialsControlPropertiesConfig : + ControlPropertiesConfig + { + /// + /// Gets or sets the ComParams + /// + [JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(ComSpecJsonConverter))] + public ComPort.ComPortSpec? ComParams { get; set; } - [JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(ComSpecJsonConverter))] - public ComPort.ComPortSpec? ComParams { get; set; } + /// + /// Gets or sets the CresnetId + /// + [JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)] + public string CresnetId { get; set; } - [JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)] - public string CresnetId { get; set; } + /// + /// Attempts to provide uint conversion of string CresnetId + /// + [JsonIgnore] + public uint CresnetIdInt + { + get + { + try + { + return Convert.ToUInt32(CresnetId, 16); + } + catch (Exception) + { + throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId)); + } + } + } - /// - /// Attempts to provide uint conversion of string CresnetId - /// - [JsonIgnore] - public uint CresnetIdInt - { - get - { - try - { - return Convert.ToUInt32(CresnetId, 16); - } - catch (Exception) - { - throw new FormatException(string.Format("ERROR:Unable to convert Cresnet ID: {0} to hex. Error:\n{1}", CresnetId)); - } - } - } + /// + /// Gets or sets the InfinetId + /// + [JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)] + public string InfinetId { get; set; } - [JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)] - public string InfinetId { get; set; } + /// + /// Attepmts to provide uiont conversion of string InifinetId + /// + [JsonIgnore] + public uint InfinetIdInt + { + get + { + try + { + return Convert.ToUInt32(InfinetId, 16); + } + catch (Exception) + { + throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId)); + } + } + } + } - /// - /// Attepmts to provide uiont conversion of string InifinetId - /// - [JsonIgnore] - public uint InfinetIdInt - { - get - { - try - { - return Convert.ToUInt32(InfinetId, 16); - } - catch (Exception) - { - throw new FormatException(string.Format("ERROR:Unable to conver Infinet ID: {0} to hex. Error:\n{1}", InfinetId)); - } - } - } -} - -public class IrControlSpec -{ - public string PortDeviceKey { get; set; } - public uint PortNumber { get; set; } - public string File { get; set; } + /// + /// Represents a IrControlSpec + /// + public class IrControlSpec + { + /// + /// Gets or sets the PortDeviceKey + /// + public string PortDeviceKey { get; set; } + /// + /// Gets or sets the PortNumber + /// + public uint PortNumber { get; set; } + /// + /// Gets or sets the File + /// + public string File { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/CommunicationExtras.cs b/src/PepperDash.Essentials.Core/Comm and IR/CommunicationExtras.cs index cc188fa7..d9bd0974 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/CommunicationExtras.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/CommunicationExtras.cs @@ -13,8 +13,8 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -24,4 +24,5 @@ namespace PepperDash.Essentials.Core; /// Gets the Device /// IComPorts Device { get; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/ConsoleCommMockDevice.cs b/src/PepperDash.Essentials.Core/Comm and IR/ConsoleCommMockDevice.cs index 91a2d76b..7c2af905 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/ConsoleCommMockDevice.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/ConsoleCommMockDevice.cs @@ -9,8 +9,11 @@ using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ConsoleCommMockDevice + /// public class ConsoleCommMockDevice : EssentialsDevice, ICommunicationMonitor { /// @@ -57,7 +60,7 @@ namespace PepperDash.Essentials.Core; /// CustomActivate method /// /// - protected override bool CustomActivate() + public override bool CustomActivate() { Communication.Connect(); CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); }; @@ -100,19 +103,31 @@ namespace PepperDash.Essentials.Core; } } -public class ConsoleCommMockDeviceFactory : EssentialsDeviceFactory -{ - public ConsoleCommMockDeviceFactory() + /// + /// Represents a ConsoleCommMockDeviceFactory + /// + public class ConsoleCommMockDeviceFactory : EssentialsDeviceFactory { - TypeNames = new List() { "commmock" }; + /// + /// Initializes a new instance of the ConsoleCommMockDeviceFactory class. + /// + public ConsoleCommMockDeviceFactory() + { + TypeNames = new List() { "commmock" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Comm Mock Device"); + var comm = CommFactory.CreateCommForDevice(dc); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject( + dc.Properties.ToString()); + return new ConsoleCommMockDevice(dc.Key, dc.Name, props, comm); + } } - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Comm Mock Device"); - var comm = CommFactory.CreateCommForDevice(dc); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject( - dc.Properties.ToString()); - return new ConsoleCommMockDevice(dc.Key, dc.Name, props, comm); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/GenericComm.cs b/src/PepperDash.Essentials.Core/Comm and IR/GenericComm.cs index 6e913968..069244c3 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/GenericComm.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/GenericComm.cs @@ -13,132 +13,164 @@ using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Serves as a generic wrapper class for all styles of IBasicCommuncation ports -/// -[Description("Generic communication wrapper class for any IBasicCommunication type")] -public class GenericComm : ReconfigurableBridgableDevice +namespace PepperDash.Essentials.Core { - EssentialsControlPropertiesConfig PropertiesConfig; - - public IBasicCommunication CommPort { get; private set; } - - public GenericComm(DeviceConfig config) - : base(config) + /// + /// Serves as a generic wrapper class for all styles of IBasicCommuncation ports + /// + [Description("Generic communication wrapper class for any IBasicCommunication type")] + public class GenericComm : ReconfigurableBridgableDevice { + EssentialsControlPropertiesConfig PropertiesConfig; - PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); + /// + /// Gets the CommPort + /// + public IBasicCommunication CommPort { get; private set; } - var commPort = CommFactory.CreateCommForDevice(config); - - //Fixing decision to require '-comPorts' in delcaration for DGE in order to get a device with comports included - if (commPort == null) + /// + /// Constructor + /// + /// the config of the device + public GenericComm(DeviceConfig config) + : base(config) { - config.Key = config.Key + "-comPorts"; - commPort = CommFactory.CreateCommForDevice(config); - } - CommPort = commPort; + PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); - } + var commPort = CommFactory.CreateCommForDevice(config); - public static IKeyed BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); - return new GenericComm(dc); - } - - public void SetPortConfig(string portConfig) - { - // TODO: Deserialize new EssentialsControlPropertiesConfig and handle as necessary - try - { - PropertiesConfig = JsonConvert.DeserializeObject - (portConfig); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error deserializing port config: {0}", e); - } - } - - protected override void CustomSetConfig(DeviceConfig config) - { - PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); - - ConfigWriter.UpdateDeviceConfig(config); - } - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new IBasicCommunicationJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); - } - - if (CommPort == null) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to link device '{0}'. CommPort is null", Key); - return; - } - - Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - - // this is a permanent event handler. This cannot be -= from event - CommPort.TextReceived += (s, a) => - { - trilist.SetString(joinMap.TextReceived.JoinNumber, a.Text); - }; - trilist.SetStringSigAction(joinMap.SendText.JoinNumber, s => CommPort.SendText(s)); - trilist.SetStringSigAction(joinMap.SetPortConfig.JoinNumber, SetPortConfig); - - - var sComm = CommPort as ISocketStatus; - if (sComm == null) return; - sComm.ConnectionChange += (s, a) => - { - trilist.SetUshort(joinMap.Status.JoinNumber, (ushort)(a.Client.ClientStatus)); - trilist.SetBool(joinMap.Connected.JoinNumber, a.Client.ClientStatus == - SocketStatus.SOCKET_STATUS_CONNECTED); - }; - - trilist.SetBoolSigAction(joinMap.Connect.JoinNumber, b => - { - if (b) + //Fixing decision to require '-comPorts' in declaration for DGE in order to get a device with comports included + if (commPort == null) { - sComm.Connect(); + config.Key = config.Key + "-comPorts"; + commPort = CommFactory.CreateCommForDevice(config); + } + + CommPort = commPort; + + } + + /// + /// BuildDevice method + /// + public static IKeyed BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); + return new GenericComm(dc); + } + + /// + /// SetPortConfig method + /// + public void SetPortConfig(string portConfig) + { + // TODO: Deserialize new EssentialsControlPropertiesConfig and handle as necessary + try + { + PropertiesConfig = JsonConvert.DeserializeObject + (portConfig); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Error deserializing port config: {0}", e); + } + } + + /// + /// CustomSetConfig method + /// + /// the new device configuration + protected override void CustomSetConfig(DeviceConfig config) + { + PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); + + ConfigWriter.UpdateDeviceConfig(config); + } + + /// + /// LinkToApi method + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new IBasicCommunicationJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); } else { - sComm.Disconnect(); + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); } - }); - } -} -public class GenericCommFactory : EssentialsDeviceFactory -{ - public GenericCommFactory() - { - TypeNames = new List() { "genericComm" }; + if (CommPort == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to link device '{0}'. CommPort is null", Key); + return; + } + + Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + // this is a permanent event handler. This cannot be -= from event + CommPort.TextReceived += (s, a) => + { + trilist.SetString(joinMap.TextReceived.JoinNumber, a.Text); + }; + trilist.SetStringSigAction(joinMap.SendText.JoinNumber, s => CommPort.SendText(s)); + trilist.SetStringSigAction(joinMap.SetPortConfig.JoinNumber, SetPortConfig); + + + var sComm = CommPort as ISocketStatus; + if (sComm == null) return; + sComm.ConnectionChange += (s, a) => + { + trilist.SetUshort(joinMap.Status.JoinNumber, (ushort)(a.Client.ClientStatus)); + trilist.SetBool(joinMap.Connected.JoinNumber, a.Client.ClientStatus == + SocketStatus.SOCKET_STATUS_CONNECTED); + }; + + trilist.SetBoolSigAction(joinMap.Connect.JoinNumber, b => + { + if (b) + { + sComm.Connect(); + } + else + { + sComm.Disconnect(); + } + }); + } } - public override EssentialsDevice BuildDevice(DeviceConfig dc) + /// + /// Represents a GenericCommFactory + /// + public class GenericCommFactory : EssentialsDeviceFactory { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); - return new GenericComm(dc); + /// + /// Initializes a new instance of the GenericCommFactory class. + /// + public GenericCommFactory() + { + TypeNames = new List() { "genericComm" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); + return new GenericComm(dc); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/GenericHttpClient.cs b/src/PepperDash.Essentials.Core/Comm and IR/GenericHttpClient.cs new file mode 100644 index 00000000..e02027b3 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Comm and IR/GenericHttpClient.cs @@ -0,0 +1,184 @@ +using Crestron.SimplSharp.Net.Http; +using PepperDash.Core; +using System; + +namespace PepperDash.Essentials.Core +{ + + /// + /// Represents a GenericHttpClient + /// + [Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")] + public class GenericHttpClient : Device, IBasicCommunication + { + private readonly HttpClient Client; + + /// + /// Event raised when response is received + /// + public event EventHandler ResponseRecived; + + /// + /// Constructor + /// + /// key of the device + /// name of the device + /// hostname for the HTTP client + public GenericHttpClient(string key, string name, string hostname) + : base(key, name) + { + Client = new HttpClient + { + HostName = hostname + }; + + + } + + /// + /// SendText method + /// + /// the path to send the request to + public void SendText(string path) + { + HttpClientRequest request = new HttpClientRequest(); + string url = string.Format("http://{0}/{1}", Client.HostName, path); + request.Url = new UrlParser(url); + HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request); + } + + /// + /// SendText method + /// + /// format for the items + /// items to format + public void SendText(string format, params object[] items) + { + HttpClientRequest request = new HttpClientRequest(); + string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items)); + request.Url = new UrlParser(url); + HttpClient.DISPATCHASYNC_ERROR error = Client.DispatchAsyncEx(request, Response, request); + } + + /// + /// SendTextNoResponse method + /// + /// format for the items + /// items to format + public void SendTextNoResponse(string format, params object[] items) + { + HttpClientRequest request = new HttpClientRequest(); + string url = string.Format("http://{0}/{1}", Client.HostName, string.Format(format, items)); + request.Url = new UrlParser(url); + Client.Dispatch(request); + } + + /// + /// Response method + /// + /// response received from the HTTP client + /// error status of the HTTP callback + /// original HTTP client request + private void Response(HttpClientResponse response, HTTP_CALLBACK_ERROR error, object request) + { + if (error == HTTP_CALLBACK_ERROR.COMPLETED) + { + var responseReceived = response; + + if (responseReceived.ContentString.Length > 0) + { + ResponseRecived?.Invoke(this, new GenericHttpClientEventArgs(responseReceived.ContentString, (request as HttpClientRequest).Url.ToString(), error)); + } + } + + } + + + #region IBasicCommunication Members + + /// + /// SendBytes method + /// + /// bytes to send + public void SendBytes(byte[] bytes) + { + throw new NotImplementedException(); + } + + + + #endregion + + #region ICommunicationReceiver Members + + /// + /// BytesReceived event + /// + public event EventHandler BytesReceived; + + /// + /// Connect method + /// + public void Connect() + { + throw new NotImplementedException(); + } + + /// + /// Disconnect method + /// + public void Disconnect() + { + throw new NotImplementedException(); + } + + /// + /// IsConnected property + /// + public bool IsConnected + { + get { return true; } + } + + /// + /// TextReceived event + /// + public event EventHandler TextReceived; + + #endregion + } + + /// + /// Represents a GenericHttpClientEventArgs + /// + public class GenericHttpClientEventArgs : EventArgs + { + /// + /// Gets or sets the ResponseText + /// + public string ResponseText { get; private set; } + + /// + /// Gets or sets the RequestPath + /// + public string RequestPath { get; private set; } + + /// + /// Gets or sets the Error + /// + public HTTP_CALLBACK_ERROR Error { get; set; } + + /// + /// Constructor + /// + /// response text + /// request path + /// error status + public GenericHttpClientEventArgs(string response, string request, HTTP_CALLBACK_ERROR error) + { + ResponseText = response; + RequestPath = request; + Error = error; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs b/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs index b4d4069d..6ca4b533 100644 --- a/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs +++ b/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs @@ -12,8 +12,8 @@ using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Helper class for IR port operations /// @@ -145,7 +145,7 @@ namespace PepperDash.Essentials.Core; var port = irDev.IROutputPorts[portNum]; - + return port; } @@ -157,82 +157,82 @@ namespace PepperDash.Essentials.Core; /// IrOutputPortController object public static IrOutputPortController GetIrOutputPortController(DeviceConfig config) { - Debug.LogMessage(LogEventLevel.Debug, "Attempting to create new Ir Port Controller"); + Debug.LogMessage(LogEventLevel.Debug, "Attempting to create new Ir Port Controller"); if (config == null) { return null; } - var postActivationFunc = new Func (GetIrOutputPort); + var postActivationFunc = new Func (GetIrOutputPort); var irDevice = new IrOutputPortController(config.Key + "-ir", postActivationFunc, config); return irDevice; } /* - /// - /// Returns a ready-to-go IrOutputPortController from a DeviceConfig object. - /// - public static IrOutputPortController GetIrOutputPortController(DeviceConfig devConf) - { - var irControllerKey = devConf.Key + "-ir"; - if (devConf.Properties == null) + /// + /// GetIrOutputPortController method + /// + public static IrOutputPortController GetIrOutputPortController(DeviceConfig devConf) { - Debug.LogMessage(LogEventLevel.Information, "[{0}] WARNING: Device config does not include properties. IR will not function.", devConf.Key); - return new IrOutputPortController(irControllerKey, null, ""); - } + var irControllerKey = devConf.Key + "-ir"; + if (devConf.Properties == null) + { + Debug.LogMessage(LogEventLevel.Information, "[{0}] WARNING: Device config does not include properties. IR will not function.", devConf.Key); + return new IrOutputPortController(irControllerKey, null, ""); + } - var control = devConf.Properties["control"]; - if (control == null) - { - var c = new IrOutputPortController(irControllerKey, null, ""); - Debug.LogMessage(LogEventLevel.Information, c, "WARNING: Device config does not include control properties. IR will not function"); - return c; - } + var control = devConf.Properties["control"]; + if (control == null) + { + var c = new IrOutputPortController(irControllerKey, null, ""); + Debug.LogMessage(LogEventLevel.Information, c, "WARNING: Device config does not include control properties. IR will not function"); + return c; + } - var portDevKey = control.Value("controlPortDevKey"); - var portNum = control.Value("controlPortNumber"); - IIROutputPorts irDev = null; + var portDevKey = control.Value("controlPortDevKey"); + var portNum = control.Value("controlPortNumber"); + IIROutputPorts irDev = null; - if (portDevKey == null) - { - var c = new IrOutputPortController(irControllerKey, null, ""); - Debug.LogMessage(LogEventLevel.Information, c, "WARNING: control properties is missing ir device"); - return c; - } + if (portDevKey == null) + { + var c = new IrOutputPortController(irControllerKey, null, ""); + Debug.LogMessage(LogEventLevel.Information, c, "WARNING: control properties is missing ir device"); + return c; + } - if (portNum == 0) - { - var c = new IrOutputPortController(irControllerKey, null, ""); - Debug.LogMessage(LogEventLevel.Information, c, "WARNING: control properties is missing ir port number"); - return c; - } + if (portNum == 0) + { + var c = new IrOutputPortController(irControllerKey, null, ""); + Debug.LogMessage(LogEventLevel.Information, c, "WARNING: control properties is missing ir port number"); + return c; + } - if (portDevKey.Equals("controlSystem", StringComparison.OrdinalIgnoreCase) - || portDevKey.Equals("processor", StringComparison.OrdinalIgnoreCase)) - irDev = Global.ControlSystem; - else - irDev = DeviceManager.GetDeviceForKey(portDevKey) as IIROutputPorts; + if (portDevKey.Equals("controlSystem", StringComparison.OrdinalIgnoreCase) + || portDevKey.Equals("processor", StringComparison.OrdinalIgnoreCase)) + irDev = Global.ControlSystem; + else + irDev = DeviceManager.GetDeviceForKey(portDevKey) as IIROutputPorts; - if (irDev == null) - { - var c = new IrOutputPortController(irControllerKey, null, ""); - Debug.LogMessage(LogEventLevel.Information, c, "WARNING: device with IR ports '{0}' not found", portDevKey); - return c; - } + if (irDev == null) + { + var c = new IrOutputPortController(irControllerKey, null, ""); + Debug.LogMessage(LogEventLevel.Information, c, "WARNING: device with IR ports '{0}' not found", portDevKey); + return c; + } - if (portNum <= irDev.NumberOfIROutputPorts) // success! - return new IrOutputPortController(irControllerKey, irDev.IROutputPorts[portNum], - IrDriverPathPrefix + control["irFile"].Value()); - else - { - var c = new IrOutputPortController(irControllerKey, null, ""); - Debug.LogMessage(LogEventLevel.Information, c, "WARNING: device '{0}' IR port {1} out of range", - portDevKey, portNum); - return c; - } - }*/ + if (portNum <= irDev.NumberOfIROutputPorts) // success! + return new IrOutputPortController(irControllerKey, irDev.IROutputPorts[portNum], + IrDriverPathPrefix + control["irFile"].Value()); + else + { + var c = new IrOutputPortController(irControllerKey, null, ""); + Debug.LogMessage(LogEventLevel.Information, c, "WARNING: device '{0}' IR port {1} out of range", + portDevKey, portNum); + return c; + } + }*/ } /// @@ -265,4 +265,5 @@ namespace PepperDash.Essentials.Core; { FileName = ""; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/AudioControlPointListItem.cs b/src/PepperDash.Essentials.Core/Config/AudioControlPointListItem.cs index 3a3e2fc8..54cc3574 100644 --- a/src/PepperDash.Essentials.Core/Config/AudioControlPointListItem.cs +++ b/src/PepperDash.Essentials.Core/Config/AudioControlPointListItem.cs @@ -6,14 +6,24 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core.Config; - -public class AudioControlPointListItem +namespace PepperDash.Essentials.Core.Config { - [JsonProperty("levelControls")] - public Dictionary LevelControls { get; set; } = new Dictionary(); + /// + /// Represents a AudioControlPointListItem + /// + public class AudioControlPointListItem + { + /// + /// Level controls for this audio control point + /// + [JsonProperty("levelControls")] + public Dictionary LevelControls { get; set; } = new Dictionary(); - [JsonProperty("presets")] - public Dictionary Presets { get; set; } = new Dictionary(); + /// + /// Presets for this audio control point + /// + [JsonProperty("presets")] + public Dictionary Presets { get; set; } = new Dictionary(); + } } diff --git a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs index 2e499ed4..435d448f 100644 --- a/src/PepperDash.Essentials.Core/Config/BasicConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/BasicConfig.cs @@ -7,8 +7,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Essentials.Core.Devices; -namespace PepperDash.Essentials.Core.Config; - +namespace PepperDash.Essentials.Core.Config +{ /// /// Override this and splice on specific room type behavior, as well as other properties /// @@ -32,32 +32,50 @@ namespace PepperDash.Essentials.Core.Config; [JsonProperty("sourceLists")] public Dictionary> SourceLists { get; set; } - [JsonProperty("destinationLists")] - public Dictionary> DestinationLists { get; set; } + /// + /// Gets or sets the DestinationLists + /// + [JsonProperty("destinationLists")] + public Dictionary> DestinationLists { get; set; } - [JsonProperty("audioControlPointLists")] - public Dictionary AudioControlPointLists { get; set; } + /// + /// Gets or sets the AudioControlPointLists + /// + [JsonProperty("audioControlPointLists")] + public Dictionary AudioControlPointLists { get; set; } - [JsonProperty("cameraLists")] - public Dictionary> CameraLists { get; set; } + /// + /// Gets or sets the CameraLists + /// + [JsonProperty("cameraLists")] + public Dictionary> CameraLists { get; set; } - [JsonProperty("tieLines")] - public List TieLines { get; set; } + /// + /// Gets or sets the TieLines + /// + [JsonProperty("tieLines")] + public List TieLines { get; set; } - [JsonProperty("joinMaps")] - public Dictionary JoinMaps { get; set; } + /// + /// Gets or sets the JoinMaps + /// + [JsonProperty("joinMaps")] + public Dictionary JoinMaps { get; set; } - public BasicConfig() - { - Info = new InfoConfig(); - Devices = new List(); - SourceLists = new Dictionary>(); - DestinationLists = new Dictionary>(); - AudioControlPointLists = new Dictionary(); - CameraLists = new Dictionary>(); - TieLines = new List(); - JoinMaps = new Dictionary(); - } + /// + /// BasicConfig Constructor + /// + public BasicConfig() + { + Info = new InfoConfig(); + Devices = new List(); + SourceLists = new Dictionary>(); + DestinationLists = new Dictionary>(); + AudioControlPointLists = new Dictionary(); + CameraLists = new Dictionary>(); + TieLines = new List(); + JoinMaps = new Dictionary(); + } /// /// Checks SourceLists for a given list and returns it if found. Otherwise, returns null @@ -70,11 +88,11 @@ namespace PepperDash.Essentials.Core.Config; return SourceLists[key]; } - /// - /// Retrieves a DestinationListItem based on the key - /// - /// key of the list to retrieve - /// DestinationList if the key exists, null otherwise + /// + /// Retrieves a DestinationListItem based on the key + /// + /// key of the list to retrieve + /// DestinationList if the key exists, null otherwise public Dictionary GetDestinationListForKey(string key) { if (DestinationLists == null || string.IsNullOrEmpty(key) || !DestinationLists.ContainsKey(key)) @@ -85,47 +103,51 @@ namespace PepperDash.Essentials.Core.Config; return DestinationLists[key]; } - /// - /// Retrieves a AudioControlPointList based on the key - /// - /// key of the list to retrieve - /// AudioControlPointList if the key exists, null otherwise - public AudioControlPointListItem GetAudioControlPointListForKey(string key) - { - if (AudioControlPointLists == null || string.IsNullOrEmpty(key) || !AudioControlPointLists.ContainsKey(key)) - return null; - - return AudioControlPointLists[key]; - } - - /// - /// Checks CameraLists for a given list and returns it if found. Otherwise, returns null - /// - public Dictionary GetCameraListForKey(string key) - { - if (CameraLists == null || string.IsNullOrEmpty(key) || !CameraLists.ContainsKey(key)) - return null; - - return CameraLists[key]; - } - - /// - /// Checks Devices for an item with a Key that matches and returns it if found. Otherwise, retunes null - /// - /// Key of desired device - /// - public DeviceConfig GetDeviceForKey(string key) - { - if (string.IsNullOrEmpty(key)) - return null; - - var deviceConfig = Devices.FirstOrDefault(d => d.Key.Equals(key)); - - if (deviceConfig != null) - return deviceConfig; - else + /// + /// Retrieves a AudioControlPointList based on the key + /// + /// key of the list to retrieve + /// AudioControlPointList if the key exists, null otherwise + /// + /// GetAudioControlPointListForKey method + /// + public AudioControlPointListItem GetAudioControlPointListForKey(string key) { - return null; + if (AudioControlPointLists == null || string.IsNullOrEmpty(key) || !AudioControlPointLists.ContainsKey(key)) + return null; + + return AudioControlPointLists[key]; + } + + /// + /// Checks CameraLists for a given list and returns it if found. Otherwise, returns null + /// + /// Key of desired camera list + public Dictionary GetCameraListForKey(string key) + { + if (CameraLists == null || string.IsNullOrEmpty(key) || !CameraLists.ContainsKey(key)) + return null; + + return CameraLists[key]; + } + + /// + /// Checks Devices for an item with a Key that matches and returns it if found. Otherwise, retunes null + /// + /// Key of desired device + public DeviceConfig GetDeviceForKey(string key) + { + if (string.IsNullOrEmpty(key)) + return null; + + var deviceConfig = Devices.FirstOrDefault(d => d.Key.Equals(key)); + + if (deviceConfig != null) + return deviceConfig; + else + { + return null; + } } - } } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/ConfigPropertiesHelpers.cs b/src/PepperDash.Essentials.Core/Config/ConfigPropertiesHelpers.cs index 9eb8e61d..99633266 100644 --- a/src/PepperDash.Essentials.Core/Config/ConfigPropertiesHelpers.cs +++ b/src/PepperDash.Essentials.Core/Config/ConfigPropertiesHelpers.cs @@ -9,23 +9,27 @@ using PepperDash.Essentials.Core; using PepperDash.Core; using Newtonsoft.Json.Linq; -namespace PepperDash.Essentials.Core.Config; - -public class ConfigPropertiesHelpers +namespace PepperDash.Essentials.Core.Config { /// - /// Returns the value of properties.hasAudio, or false if not defined + /// Represents a ConfigPropertiesHelpers /// - public static bool GetHasAudio(DeviceConfig deviceConfig) + public class ConfigPropertiesHelpers { - return deviceConfig.Properties.Value("hasAudio"); - } + /// + /// GetHasAudio method + /// + public static bool GetHasAudio(DeviceConfig deviceConfig) + { + return deviceConfig.Properties.Value("hasAudio"); + } - /// - /// Returns the value of properties.hasControls, or false if not defined - /// - public static bool GetHasControls(DeviceConfig deviceConfig) - { - return deviceConfig.Properties.Value("hasControls"); + /// + /// Returns the value of properties.hasControls, or false if not defined + /// + public static bool GetHasControls(DeviceConfig deviceConfig) + { + return deviceConfig.Properties.Value("hasControls"); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/DeviceConfig.cs b/src/PepperDash.Essentials.Core/Config/DeviceConfig.cs index 8e38fcfa..b8620bb9 100644 --- a/src/PepperDash.Essentials.Core/Config/DeviceConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/DeviceConfig.cs @@ -11,71 +11,116 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Config; - -public class DeviceConfig +namespace PepperDash.Essentials.Core.Config { - [JsonProperty("key")] - public string Key { get; set; } - - [JsonProperty("uid")] - public int Uid { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("group")] - public string Group { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("properties")] - [JsonConverter(typeof(DevicePropertiesConverter))] - public JToken Properties { get; set; } - - public DeviceConfig(DeviceConfig dc) + /// + /// Represents a DeviceConfig + /// + public class DeviceConfig { - Key = dc.Key; - Uid = dc.Uid; - Name = dc.Name; - Group = dc.Group; - Type = dc.Type; + /// + /// Gets or sets the Key + /// + [JsonProperty("key")] + public string Key { get; set; } - Properties = JToken.Parse(dc.Properties.ToString()); + /// + /// Gets or sets the Uid + /// + [JsonProperty("uid")] + public int Uid { get; set; } - //Properties = JToken.FromObject(dc.Properties); - } + /// + /// Gets or sets the Name + /// + [JsonProperty("name")] + public string Name { get; set; } - public DeviceConfig() {} -} + /// + /// Gets or sets the Group + /// + [JsonProperty("group")] + public string Group { get; set; } -/// -/// -/// -public class DevicePropertiesConverter : JsonConverter -{ + /// + /// Gets or sets the Type + /// + [JsonProperty("type")] + public string Type { get; set; } - public override bool CanConvert(Type objectType) - { - return objectType == typeof(JToken); - } + /// + /// Gets or sets the Properties + /// + [JsonProperty("properties")] + [JsonConverter(typeof(DevicePropertiesConverter))] + public JToken Properties { get; set; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return JToken.ReadFrom(reader); - } - - public override bool CanWrite - { - get + /// + /// Constructor + /// + /// device config + public DeviceConfig(DeviceConfig dc) { - return false; + Key = dc.Key; + Uid = dc.Uid; + Name = dc.Name; + Group = dc.Group; + Type = dc.Type; + + Properties = JToken.Parse(dc.Properties.ToString()); + + //Properties = JToken.FromObject(dc.Properties); + } + + /// + /// Default Constructor + /// + public DeviceConfig() { } + } + + /// + /// Represents a DevicePropertiesConverter + /// + public class DevicePropertiesConverter : JsonConverter + { + + /// + /// CanConvert method + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(JToken); + } + + /// + /// ReadJson method + /// + /// reader to use + /// type of object being read + /// existing value for the object + /// serializer to use + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return JToken.ReadFrom(reader); + } + + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// WriteJson method + /// + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException("SOD OFF HOSER"); } } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs index 0dd39fee..37cd4eba 100644 --- a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs +++ b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigReader.cs @@ -1,15 +1,18 @@ -using System; + + +using System; using System.Linq; using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core.Config; - +namespace PepperDash.Essentials.Core.Config +{ /// /// Loads the ConfigObject from the file /// @@ -18,8 +21,8 @@ namespace PepperDash.Essentials.Core.Config; /// /// Local Config Present Message /// - public const string LocalConfigPresentBanner = - @" + public const string LocalConfigPresent = + @" *************************************************** ************* Using Local config file ************* ***************************************************"; @@ -32,261 +35,256 @@ namespace PepperDash.Essentials.Core.Config; /// /// LoadConfig2 method /// - public static bool LoadConfig() + public static bool LoadConfig2() { Debug.LogMessage(LogEventLevel.Information, "Loading unmerged system/template portal configuration file."); try { - // Check for local config file first - var filePath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder + Global.DirectorySeparator + Global.ConfigFileName; + // Check for local config file first + var filePath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder + Global.DirectorySeparator + Global.ConfigFileName; - bool localConfigFound = false; + bool localConfigFound = false; - Debug.LogMessage(LogEventLevel.Information, "Attempting to load Local config file: '{0}'", filePath); + Debug.LogMessage(LogEventLevel.Information, "Attempting to load Local config file: '{0}'", filePath); - // Check for local config directory first + // Check for local config directory first - var configFiles = GetConfigFiles(filePath); - - if (configFiles != null) - { - if (configFiles.Length > 1) - { - Debug.LogMessage(LogEventLevel.Information, - "****Error: Multiple Local Configuration files present. Please ensure only a single file exists and reset program.****"); - return false; - } - if(configFiles.Length == 1) - { - localConfigFound = true; - - } - } - else - { - Debug.LogMessage(LogEventLevel.Information, - "Local Configuration file not present.", filePath); - - } - - // Check for Portal Config - if(!localConfigFound) - { - filePath = Global.FilePathPrefix + Global.ConfigFileName; - - Debug.LogMessage(LogEventLevel.Information, "Attempting to load Portal config file: '{0}'", filePath); - - configFiles = GetConfigFiles(filePath); + var configFiles = GetConfigFiles(filePath); if (configFiles != null) { - Debug.LogMessage(LogEventLevel.Verbose, "{0} config files found matching pattern", configFiles.Length); - if (configFiles.Length > 1) { Debug.LogMessage(LogEventLevel.Information, - "****Error: Multiple Portal Configuration files present. Please ensure only a single file exists and reset program.****"); + "****Error: Multiple Local Configuration files present. Please ensure only a single file exists and reset program.****"); return false; } - else if (configFiles.Length == 1) + if(configFiles.Length == 1) { - Debug.LogMessage(LogEventLevel.Information, "Found Portal config file: '{0}'", filePath); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "No config file found."); - return false; + localConfigFound = true; + } } else { Debug.LogMessage(LogEventLevel.Information, - "ERROR: Portal Configuration file not present. Please load file and reset program."); - return false; + "Local Configuration file not present.", filePath); + } - } - // Get the actual file path - filePath = configFiles[0].FullName; - - // Generate debug statement if using a local file. - if (localConfigFound) - { - GetLocalFileMessage(filePath); - } - - // Read the file - using (StreamReader fs = new StreamReader(filePath)) - { - Debug.LogMessage(LogEventLevel.Information, "Loading config file: '{0}'", filePath); - - var fileContents = fs.ReadToEnd(); - - if (localConfigFound) + // Check for Portal Config + if(!localConfigFound) { - ConfigObject = JObject.Parse(fileContents).ToObject(); - Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config"); - return ConfigObject != null; - } - else - { - var parsedConfig = JObject.Parse(fileContents); + filePath = Global.FilePathPrefix + Global.ConfigFileName; - // A config is v1 if it has separate "system" and "template" nodes that - // need to be merged. A v2 config is already merged by the Portal API and - // will not have "system"/"template" nodes. This is independent of whether - // a "versions" node is present, which only carries version metadata and - // can appear on either a v1 or v2 config. - var isV1Config = parsedConfig["system"] != null && parsedConfig["template"] != null; + Debug.LogMessage(LogEventLevel.Information, "Attempting to load Portal config file: '{0}'", filePath); - if (!isV1Config) + configFiles = GetConfigFiles(filePath); + + if (configFiles != null) { - Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary."); - ConfigObject = parsedConfig.ToObject(); - Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config"); - return ConfigObject != null; + Debug.LogMessage(LogEventLevel.Verbose, "{0} config files found matching pattern", configFiles.Length); + + if (configFiles.Length > 1) + { + Debug.LogMessage(LogEventLevel.Information, + "****Error: Multiple Portal Configuration files present. Please ensure only a single file exists and reset program.****"); + return false; + } + else if (configFiles.Length == 1) + { + Debug.LogMessage(LogEventLevel.Information, "Found Portal config file: '{0}'", filePath); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "No config file found."); + return false; + } } - - Debug.LogMessage(LogEventLevel.Information, "Config file is a v1 format, merging system and template."); - - // Extract SystemUrl and TemplateUrl into final config output - ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject(); - - if (ConfigObject == null) + else { - Debug.LogMessage(LogEventLevel.Warning, "Config merge produced a null ConfigObject."); + Debug.LogMessage(LogEventLevel.Information, + "ERROR: Portal Configuration file not present. Please load file and reset program."); return false; } - - if (parsedConfig["system_url"] != null) - { - ConfigObject.SystemUrl = parsedConfig["system_url"].Value(); - } - - if (parsedConfig["template_url"] != null) - { - ConfigObject.TemplateUrl = parsedConfig["template_url"].Value(); - } - - // MergeConfigs does not carry the "versions" node forward, so it must be - // applied separately to ensure it's preserved in the merged config. - if (parsedConfig["versions"] != null) - { - ConfigObject.Versions = parsedConfig["versions"].ToObject(); - } } - Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Merged Config"); + // Get the actual file path + filePath = configFiles[0].FullName; - return true; - } + // Generate debug statement if using a local file. + if (localConfigFound) + { + GetLocalFileMessage(filePath); + } + + // Read the file + using (StreamReader fs = new StreamReader(filePath)) + { + Debug.LogMessage(LogEventLevel.Information, "Loading config file: '{0}'", filePath); + + if (localConfigFound) + { + ConfigObject = JObject.Parse(fs.ReadToEnd()).ToObject(); + + Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Local Config"); + + return true; + } + else + { + var parsedConfig = JObject.Parse(fs.ReadToEnd()); + + // A config is v1 if it has separate "system" and "template" nodes that + // need to be merged. A v2 config is already merged by the Portal API and + // will not have "system"/"template" nodes. This is independent of whether + // a "versions" node is present, which only carries version metadata and + // can appear on either a v1 or v2 config. + var isV1Config = parsedConfig["system"] != null && parsedConfig["template"] != null; + + if (!isV1Config) + { + Debug.LogMessage(LogEventLevel.Information, "Config file is a v2 format, no merge necessary."); + ConfigObject = parsedConfig.ToObject(); + Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded v2 Config"); + return true; + } + + Debug.LogMessage(LogEventLevel.Information, "Config file is a v1 format, merging system and template."); + + // Extract SystemUrl and TemplateUrl into final config output + ConfigObject = PortalConfigReader.MergeConfigs(parsedConfig).ToObject(); + + if (parsedConfig["system_url"] != null) + { + ConfigObject.SystemUrl = parsedConfig["system_url"].Value(); + } + + if (parsedConfig["template_url"] != null) + { + ConfigObject.TemplateUrl = parsedConfig["template_url"].Value(); + } + + // MergeConfigs does not carry the "versions" node forward, so it must be + // applied separately to ensure it's preserved in the merged config. + if (parsedConfig["versions"] != null) + { + ConfigObject.Versions = parsedConfig["versions"].ToObject(); + } + } + + Debug.LogMessage(LogEventLevel.Information, "Successfully Loaded Merged Config"); + + return true; + } } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "ERROR: Config load failed: \r{0}", e); + Debug.LogMessage(LogEventLevel.Information, "ERROR: Config load failed: \r{0}", e); return false; } } - /// - /// Returns all the files from the directory specified. - /// - /// - /// - public static FileInfo[] GetConfigFiles(string filePath) - { - // Get the directory - var dir = Path.GetDirectoryName(filePath); - - if (Directory.Exists(dir)) + /// + /// Returns all the files from the directory specified. + /// + /// path to the directory + /// config files + public static FileInfo[] GetConfigFiles(string filePath) { - Debug.LogMessage(LogEventLevel.Debug, "Searching in Directory '{0}'", dir); - // Get the directory info - var dirInfo = new DirectoryInfo(dir); + // Get the directory + var dir = Path.GetDirectoryName(filePath); - // Get the file name - var fileName = Path.GetFileName(filePath); - Debug.LogMessage(LogEventLevel.Debug, "For Config Files matching: '{0}'", fileName); + if (Directory.Exists(dir)) + { + Debug.LogMessage(LogEventLevel.Debug, "Searching in Directory '{0}'", dir); + // Get the directory info + var dirInfo = new DirectoryInfo(dir); - // Get the files that match from the directory - return dirInfo.GetFiles(fileName); + // Get the file name + var fileName = Path.GetFileName(filePath); + Debug.LogMessage(LogEventLevel.Debug, "For Config Files matching: '{0}'", fileName); + + // Get the files that match from the directory + return dirInfo.GetFiles(fileName); + } + else + { + Debug.LogMessage(LogEventLevel.Information, + "Directory not found: ", dir); + + return null; + } } - else - { - Debug.LogMessage(LogEventLevel.Information, - "Directory not found: ", dir); - - return null; - } - } /// /// Returns the group for a given device key in config /// - /// - /// - public static string GetGroupForDeviceKey(string key) - { - var dev = ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - return dev == null ? null : dev.Group; - } + /// Key of the device + /// Group name if the device is found, null otherwise + public static string GetGroupForDeviceKey(string key) + { + var dev = ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + return dev == null ? null : dev.Group; + } private static void GetLocalFileMessage(string filePath) { - var filePathLength = filePath.Length + 2; - var debugStringWidth = filePathLength + 12; + var filePathLength = filePath.Length + 2; + var debugStringWidth = filePathLength + 12; - if (debugStringWidth < 51) - { - debugStringWidth = 51; - } - var qualifier = (filePathLength % 2 != 0) - ? " Using Local Config File " - : " Using Local Config File "; - var bookend1 = (debugStringWidth - qualifier.Length) / 2; - var bookend2 = (debugStringWidth - filePathLength) / 2; + if (debugStringWidth < 51) + { + debugStringWidth = 51; + } + var qualifier = (filePathLength % 2 != 0) + ? " Using Local Config File " + : " Using Local Config File "; + var bookend1 = (debugStringWidth - qualifier.Length) / 2; + var bookend2 = (debugStringWidth - filePathLength) / 2; var newDebugString = new StringBuilder() .Append(CrestronEnvironment.NewLine) - // Line 1 + // Line 1 .Append(new string('*', debugStringWidth)) .Append(CrestronEnvironment.NewLine) - // Line 2 + // Line 2 .Append(new string('*', debugStringWidth)) .Append(CrestronEnvironment.NewLine) - // Line 3 + // Line 3 .Append(new string('*', 2)) .Append(new string(' ', debugStringWidth - 4)) .Append(new string('*', 2)) .Append(CrestronEnvironment.NewLine) - // Line 4 + // Line 4 .Append(new string('*', 2)) .Append(new string(' ', bookend1 - 2)) .Append(qualifier) .Append(new string(' ', bookend1 - 2)) .Append(new string('*', 2)) .Append(CrestronEnvironment.NewLine) - // Line 5 + // Line 5 .Append(new string('*', 2)) .Append(new string(' ', bookend2 - 2)) .Append(" " + filePath + " ") .Append(new string(' ', bookend2 - 2)) .Append(new string('*', 2)) .Append(CrestronEnvironment.NewLine) - // Line 6 + // Line 6 .Append(new string('*', 2)) .Append(new string(' ', debugStringWidth - 4)) .Append(new string('*', 2)) .Append(CrestronEnvironment.NewLine) - // Line 7 + // Line 7 .Append(new string('*', debugStringWidth)) .Append(CrestronEnvironment.NewLine) - // Line 8 + // Line 8 .Append(new string('*', debugStringWidth)); - Debug.LogMessage(LogEventLevel.Verbose, "Found Local config file: '{0}'", filePath); - Debug.LogMessage(LogEventLevel.Information, newDebugString.ToString()); + Debug.LogMessage(LogEventLevel.Verbose, "Found Local config file: '{0}'", filePath); + Debug.LogMessage(LogEventLevel.Information, newDebugString.ToString()); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigUpdater.cs b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigUpdater.cs new file mode 100644 index 00000000..2ba33df0 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigUpdater.cs @@ -0,0 +1,281 @@ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Crestron.SimplSharp.Net.Http; +using Crestron.SimplSharpPro.Diagnostics; + +using PepperDash.Core; +using Serilog.Events; + +namespace PepperDash.Essentials.Core.Config +{ + /// + /// ConfigUpdater class + /// + [Obsolete("ConfigUpdater is no longer supported and will be removed in a future release.")] + public static class ConfigUpdater + { + /// + /// ConfigStatusChanged event + /// + public static event EventHandler ConfigStatusChanged; + + /// + /// GetConfigFromServer method + /// + /// URL of the config server + public static void GetConfigFromServer(string url) + { + Debug.LogMessage(LogEventLevel.Information, "Attempting to get new config from '{0}'", url); + + // HTTP GET + var req = new HttpClientRequest(); + + try + { + req.RequestType = RequestType.Get; + req.Url.Parse(url); + + new HttpClient().DispatchAsync(req, (r, e) => + { + if (e == HTTP_CALLBACK_ERROR.COMPLETED) + { + if (r.Code == 200) + { + var newConfig = r.ContentString; + + OnStatusUpdate(eUpdateStatus.ConfigFileReceived); + + ArchiveExistingPortalConfigs(); + + CheckForLocalConfigAndDelete(); + + WriteConfigToFile(newConfig); + + RestartProgram(); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Config Update Process Stopped. Failed to get config file from server: {0}", r.Code); + OnStatusUpdate(eUpdateStatus.UpdateFailed); + } + } + else + Debug.LogMessage(LogEventLevel.Information, "Request for config from Server Failed: {0}", e); + }); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, "Error Getting Config from Server: {0}", e); + } + + } + + static void OnStatusUpdate(eUpdateStatus status) + { + var handler = ConfigStatusChanged; + + if (handler != null) + { + handler(typeof(ConfigUpdater), new ConfigStatusEventArgs(status)); + } + } + + static void WriteConfigToFile(string configData) + { + var filePath = Global.FilePathPrefix + "configurationFile-updated.json"; + + try + { + var config = JObject.Parse(configData).ToObject(); + + ConfigWriter.WriteFile(filePath, configData); + + OnStatusUpdate(eUpdateStatus.WritingConfigFile); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, "Error parsing new config: {0}", e); + + OnStatusUpdate(eUpdateStatus.UpdateFailed); + } + } + + /// + /// Checks for any existing portal config files and archives them + /// + static void ArchiveExistingPortalConfigs() + { + var filePath = Global.FilePathPrefix + Global.ConfigFileName; + + var configFiles = ConfigReader.GetConfigFiles(filePath); + + if (configFiles != null) + { + Debug.LogMessage(LogEventLevel.Information, "Existing config files found. Moving to Archive folder."); + + OnStatusUpdate(eUpdateStatus.ArchivingConfigs); + + MoveFilesToArchiveFolder(configFiles); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "No Existing config files found in '{0}'. Nothing to archive", filePath); + } + } + + /// + /// Checks for presence of archive folder and if found deletes contents. + /// Moves any config files to the archive folder and adds a .bak suffix + /// + /// + static void MoveFilesToArchiveFolder(FileInfo[] files) + { + string archiveDirectoryPath = Global.FilePathPrefix + "archive"; + + if (!Directory.Exists(archiveDirectoryPath)) + { + // Directory does not exist, create it + Directory.Create(archiveDirectoryPath); + } + else + { + // Directory exists, first clear any contents + var archivedConfigFiles = ConfigReader.GetConfigFiles(archiveDirectoryPath + Global.DirectorySeparator + Global.ConfigFileName + ".bak"); + + if (archivedConfigFiles != null && archivedConfigFiles.Length > 0) + { + Debug.LogMessage(LogEventLevel.Information, "{0} Existing files found in archive folder. Deleting.", archivedConfigFiles.Length); + + for (int i = 0; i < archivedConfigFiles.Length; i++) + { + var file = archivedConfigFiles[i]; + Debug.LogMessage(LogEventLevel.Information, "Deleting archived file: '{0}'", file.FullName); + file.Delete(); + } + } + + } + + // Move any files from the program folder to the archive folder + foreach (var file in files) + { + Debug.LogMessage(LogEventLevel.Information, "Moving config file '{0}' to archive folder", file.FullName); + + // Moves the file and appends the .bak extension + var fileDest = archiveDirectoryPath + "/" + file.Name + ".bak"; + if (!File.Exists(fileDest)) + { + file.MoveTo(fileDest); + } + else + Debug.LogMessage(LogEventLevel.Information, "Cannot move file to archive folder. Existing file already exists with same name: '{0}'", fileDest); + } + } + + /// + /// Checks for LocalConfig folder in file system and deletes if found + /// + static void CheckForLocalConfigAndDelete() + { + var folderPath = Global.FilePathPrefix + ConfigWriter.LocalConfigFolder; + + if (Directory.Exists(folderPath)) + { + OnStatusUpdate(eUpdateStatus.DeletingLocalConfig); + Directory.Delete(folderPath); + Debug.LogMessage(LogEventLevel.Information, "Local Config Found in '{0}'. Deleting.", folderPath); + } + } + + /// + /// Connects to the processor via SSH and restarts the program + /// + static void RestartProgram() + { + Debug.LogMessage(LogEventLevel.Information, "Attempting to Reset Program"); + + OnStatusUpdate(eUpdateStatus.RestartingProgram); + + string response = string.Empty; + + CrestronConsole.SendControlSystemCommand(string.Format("progreset -p:{0}", InitialParametersClass.ApplicationNumber), ref response); + + Debug.LogMessage(LogEventLevel.Debug, "Console Response: {0}", response); + } + + } + + /// + /// Enumeration of eUpdateStatus values + /// + public enum eUpdateStatus + { + /// + /// UpdateStarted status + /// + UpdateStarted, + + /// + /// ConfigFileReceived status + /// + ConfigFileReceived, + + /// + /// ArchivingConfigs status + /// + ArchivingConfigs, + + /// + /// DeletingLocalConfig status + /// + DeletingLocalConfig, + + /// + /// WritingConfigFile status + /// + WritingConfigFile, + + /// + /// RestartingProgram status + /// + RestartingProgram, + + /// + /// UpdateSucceeded status + /// + UpdateSucceeded, + + /// + /// UpdateFailed status + /// + UpdateFailed + } + + /// + /// Represents a ConfigStatusEventArgs + /// + public class ConfigStatusEventArgs : EventArgs + { + /// + /// Gets or sets the UpdateStatus + /// + public eUpdateStatus UpdateStatus { get; private set; } + + /// + /// ConfigStatusEventArgs Constructor + /// + /// + public ConfigStatusEventArgs(eUpdateStatus status) + { + UpdateStatus = status; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigWriter.cs b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigWriter.cs index 423e05be..93517a3c 100644 --- a/src/PepperDash.Essentials.Core/Config/Essentials/ConfigWriter.cs +++ b/src/PepperDash.Essentials.Core/Config/Essentials/ConfigWriter.cs @@ -1,182 +1,184 @@  using System; +using System.Collections.Generic; using System.Linq; -using System.Threading; -using Timer = System.Timers.Timer; +using System.Text; +using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Config; - -/// -/// Responsible for updating config at runtime, and writing the updates out to a local file -/// -public class ConfigWriter +namespace PepperDash.Essentials.Core.Config { /// - /// The name of the subfolder where the config file will be written + /// Responsible for updating config at runtime, and writing the updates out to a local file /// - public const string LocalConfigFolder = "LocalConfig"; - - /// - /// The amount of time in milliseconds to wait after the last config update before writing the config file. This is to prevent multiple rapid updates from causing multiple file writes. - /// Default is 30 seconds. - /// - public const long WriteTimeoutInMs = 30000; - - private static Timer WriteTimer; - static readonly object _fileLock = new(); - - - static ConfigWriter() + public class ConfigWriter { - WriteTimer = new Timer(WriteTimeoutInMs); - WriteTimer.Elapsed += (s, e) => WriteConfigFile(null); + /// + /// LocalConfigFolder constant + /// + public const string LocalConfigFolder = "LocalConfig"; - } + /// + /// WriteTimeout constant + /// + public const long WriteTimeout = 30000; - /// - /// Updates the config properties of a device - /// - /// - /// - /// - public static bool UpdateDeviceProperties(string deviceKey, JToken properties) - { - bool success = false; + /// + /// WriteTimer variable + /// + public static CTimer WriteTimer; - // Get the current device config - var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(deviceKey)); + static CCriticalSection fileLock = new CCriticalSection(); - if (deviceConfig != null) + /// + /// Updates the config properties of a device + /// + /// The key of the device to update + /// The new properties for the device + /// True if the update was successful, otherwise false + public static bool UpdateDeviceProperties(string deviceKey, JToken properties) { - // Replace the current properties JToken with the new one passed into this method - deviceConfig.Properties = properties; + bool success = false; - Debug.LogMessage(LogEventLevel.Debug, "Updated properties of device: '{0}'", deviceKey); + // Get the current device config + var deviceConfig = ConfigReader.ConfigObject.Devices.FirstOrDefault(d => d.Key.Equals(deviceKey)); - success = true; - } - - ResetTimer(); - - return success; - } - - /// - /// Updates the config properties of a device - /// - public static bool UpdateDeviceConfig(DeviceConfig config) - { - bool success = false; - - var deviceConfigIndex = ConfigReader.ConfigObject.Devices.FindIndex(d => d.Key.Equals(config.Key)); - - if (deviceConfigIndex >= 0) - { - ConfigReader.ConfigObject.Devices[deviceConfigIndex] = config; - - Debug.LogMessage(LogEventLevel.Debug, "Updated config of device: '{0}'", config.Key); - - success = true; - } - - ResetTimer(); - - return success; - } - - /// - /// Updates the config properties of a room - /// - public static bool UpdateRoomConfig(DeviceConfig config) - { - bool success = false; - - var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key)); - - if (roomConfigIndex >= 0) - { - ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config; - - Debug.LogMessage(LogEventLevel.Debug, "Updated config of room: '{0}'", config.Key); - - success = true; - } - - ResetTimer(); - - return success; - } - - /// - /// Resets (or starts) the write timer - /// - static void ResetTimer() - { - WriteTimer.Stop(); - WriteTimer.Interval = WriteTimeoutInMs; - WriteTimer.Start(); - - Debug.LogMessage(LogEventLevel.Debug, "Config File write timer has been reset."); - } - - /// - /// Writes the current config to a file in the LocalConfig subfolder - /// - /// - private static void WriteConfigFile(object o) - { - var filePath = Global.FilePathPrefix + LocalConfigFolder + Global.DirectorySeparator + "configurationFile.json"; - - var configData = JsonConvert.SerializeObject(ConfigReader.ConfigObject); - - WriteFile(filePath, configData); - } - - /// - /// Writes the specified configuration data to a file. - /// - /// The path of the file to write to. - /// The configuration data to write. - public static void WriteFile(string filePath, string configData) - { - if (WriteTimer != null) - WriteTimer.Stop(); - - Debug.LogMessage(LogEventLevel.Information, "Writing Configuration to file"); - - Debug.LogMessage(LogEventLevel.Information, "Attempting to write config file: '{0}'", filePath); - - var lockAcquired = false; - try - { - lockAcquired = Monitor.TryEnter(_fileLock); - if (lockAcquired) + if (deviceConfig != null) { - using (StreamWriter sw = new StreamWriter(filePath)) + // Replace the current properties JToken with the new one passed into this method + deviceConfig.Properties = properties; + + Debug.LogMessage(LogEventLevel.Debug, "Updated properties of device: '{0}'", deviceKey); + + success = true; + } + + ResetTimer(); + + return success; + } + + /// + /// UpdateDeviceConfig method + /// + /// The new device config + /// True if the update was successful, otherwise false + public static bool UpdateDeviceConfig(DeviceConfig config) + { + bool success = false; + + var deviceConfigIndex = ConfigReader.ConfigObject.Devices.FindIndex(d => d.Key.Equals(config.Key)); + + if (deviceConfigIndex >= 0) + { + ConfigReader.ConfigObject.Devices[deviceConfigIndex] = config; + + Debug.LogMessage(LogEventLevel.Debug, "Updated config of device: '{0}'", config.Key); + + success = true; + } + + ResetTimer(); + + return success; + } + + /// + /// UpdateRoomConfig method + /// + /// The new room config + /// True if the update was successful, otherwise false + public static bool UpdateRoomConfig(DeviceConfig config) + { + bool success = false; + + var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key)); + + if (roomConfigIndex >= 0) + { + ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config; + + Debug.LogMessage(LogEventLevel.Debug, "Updated room of device: '{0}'", config.Key); + + success = true; + } + + ResetTimer(); + + return success; + } + + /// + /// Resets (or starts) the write timer + /// + static void ResetTimer() + { + if (WriteTimer == null) + WriteTimer = new CTimer(WriteConfigFile, WriteTimeout); + + WriteTimer.Reset(WriteTimeout); + + Debug.LogMessage(LogEventLevel.Debug, "Config File write timer has been reset."); + } + + /// + /// Writes the current config to a file in the LocalConfig subfolder + /// + private static void WriteConfigFile(object o) + { + var filePath = Global.FilePathPrefix + LocalConfigFolder + Global.DirectorySeparator + "configurationFile.json"; + + var configData = JsonConvert.SerializeObject(ConfigReader.ConfigObject); + + WriteFile(filePath, configData); + } + + /// + /// Writes the current config data to a file + /// + /// The file path to write to + /// The config data to write + public static void WriteFile(string filePath, string configData) + { + if (WriteTimer != null) + WriteTimer.Stop(); + + Debug.LogMessage(LogEventLevel.Information, "Writing Configuration to file"); + + Debug.LogMessage(LogEventLevel.Information, "Attempting to write config file: '{0}'", filePath); + + try + { + if (fileLock.TryEnter()) { - sw.Write(configData); - sw.Flush(); + using (StreamWriter sw = new StreamWriter(filePath)) + { + sw.Write(configData); + sw.Flush(); + } + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Unable to enter FileLock"); } } - else + catch (Exception e) { - Debug.LogMessage(LogEventLevel.Information, "Unable to enter FileLock"); + Debug.LogMessage(LogEventLevel.Information, "Error: Config write failed: \r{0}", e); + } + finally + { + if (fileLock != null && !fileLock.Disposed) + fileLock.Leave(); + } } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Error: Config write failed: \r{0}", e); - } - finally - { - if (lockAcquired) - Monitor.Exit(_fileLock); - } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs b/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs index bcfd6227..44275ef0 100644 --- a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.cs @@ -9,183 +9,197 @@ using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Config; - -/// -/// Loads the ConfigObject from the file -/// -public class EssentialsConfig : BasicConfig +namespace PepperDash.Essentials.Core.Config { /// - /// Gets or sets the System URL for the Essentials configuration + /// Loads the ConfigObject from the file /// - [JsonProperty("system_url")] - public string SystemUrl { get; set; } - - /// - /// Gets or sets the Template URL for the Essentials configuration - /// - [JsonProperty("template_url")] - public string TemplateUrl { get; set; } - - /// - /// Gets the SystemUuid extracted from the SystemUrl - /// - [JsonProperty("systemUuid")] - public string SystemUuid + public class EssentialsConfig : BasicConfig { - get - { - if (string.IsNullOrEmpty(SystemUrl)) - return "missing url"; + /// + /// Gets or sets the SystemUrl + /// + [JsonProperty("system_url")] + public string SystemUrl { get; set; } - if (SystemUrl.Contains("#")) + /// + /// Gets or sets the TemplateUrl + /// + [JsonProperty("template_url")] + public string TemplateUrl { get; set; } + + /// + /// Gets the SystemUuid extracted from the SystemUrl + /// + [JsonProperty("systemUuid")] + public string SystemUuid + { + get { - var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); - string uuid = result.Groups[1].Value; + string uuid; + + if (string.IsNullOrEmpty(SystemUrl)) + { + uuid = "missing url"; + } + else if (SystemUrl.Contains("#")) + { + var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*"); + uuid = result.Groups[1].Value; + } + else if (SystemUrl.Contains("detail")) + { + var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/detail\/(.*)\/.*"); + uuid = result.Groups[1].Value; + } + else + { + var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*"); + uuid = result.Groups[1].Value; + } + return uuid; } - else + } + + /// + /// Gets the TemplateUuid extracted from the TemplateUrl + /// + [JsonProperty("templateUuid")] + public string TemplateUuid + { + get { - var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*"); - string uuid = result.Groups[1].Value; + string uuid; + + if (string.IsNullOrEmpty(TemplateUrl)) + { + uuid = "missing template url"; + } + else if (TemplateUrl.Contains("#")) + { + var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*"); + uuid = result.Groups[1].Value; + } + else if (TemplateUrl.Contains("detail")) + { + var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/detail\/(.*)\/system-template-versions\/detail\/(.*)\/.*"); + uuid = result.Groups[2].Value; + } + else + { + var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*"); + uuid = result.Groups[2].Value; + } + return uuid; } } + + /// + /// Gets or sets the Rooms + /// + [JsonProperty("rooms")] + public List Rooms { get; set; } + + /// + /// Gets or sets the Versions + /// + [JsonProperty("versions")] + public VersionData Versions { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public EssentialsConfig() + : base() + { + Rooms = new List(); + } } /// - /// Gets the TemplateUuid extracted from the TemplateUrl + /// Represents version data for Essentials and its packages /// - [JsonProperty("templateUuid")] - public string TemplateUuid + public class VersionData { - get - { - if (string.IsNullOrEmpty(TemplateUrl)) - return "missing template url"; + /// + /// Gets or sets the Essentials version + /// + [JsonProperty("essentials")] + public NugetVersion Essentials { get; set; } - if (TemplateUrl.Contains("#")) - { - var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*"); - string uuid = result.Groups[1].Value; - return uuid; - } - else - { - var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*"); - string uuid = result.Groups[2].Value; - return uuid; - } + /// + /// Gets or sets the list of Packages + /// + [JsonProperty("packages")] + public List Packages { get; set; } + + /// + /// Gets or sets the touchpanel wrapper app version + /// + [JsonProperty("touchpanelWrapperApp")] + public NugetVersion TouchpanelWrapperApp { get; set; } + + /// + /// Gets or sets the list of user interface packages + /// + [JsonProperty("userInterfaces")] + public List UserInterfaces { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public VersionData() + { + Packages = new List(); + UserInterfaces = new List(); } } /// - /// Gets or sets the list of rooms (device configurations) + /// Represents a NugetVersion /// - [JsonProperty("rooms")] - public List Rooms { get; set; } - - /// - /// Gets or sets the Versions - /// - [JsonProperty("versions")] - public VersionData Versions { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public EssentialsConfig() - : base() + public class NugetVersion { - Rooms = new List(); + /// + /// Gets or sets the Version + /// + [JsonProperty("version")] + public string Version { get; set; } + + /// + /// Gets or sets the PackageId + /// + [JsonProperty("packageId", NullValueHandling = NullValueHandling.Ignore)] + public string PackageId { get; set; } + + /// + /// Gets or sets the RepoUrl + /// + [JsonProperty("repoUrl", NullValueHandling = NullValueHandling.Ignore)] + public string RepoUrl { get; set; } + + /// + /// Gets or sets the human-readable name + /// + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } } -} - -/// -/// Represents version data for Essentials and its packages -/// -public class VersionData -{ - /// - /// Gets or sets the Essentials version - /// - [JsonProperty("essentials")] - public PackageVersion Essentials { get; set; } /// - /// Gets or sets the list of UserInterfaces + /// Represents a SystemTemplateConfigs /// - [JsonProperty("userInterfaces")] - public List UserInterfaces { get; set; } - - /// - /// Gets or sets the TouchpanelWrapperApp version - /// This is the .ch5z app that gets loaded to a Crestron touch panel that allows it - /// to run an HTML5 user interface. - /// - [JsonProperty("touchpanelWrapperApp")] - public PackageVersion TouchpanelWrapperApp { get; set; } - - /// - /// Gets or sets the list of Packages - /// - [JsonProperty("packages")] - public List Packages { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public VersionData() + public class SystemTemplateConfigs { - UserInterfaces = new List(); - TouchpanelWrapperApp = new PackageVersion(); - Packages = new List(); + /// + /// Gets or sets the System + /// + public EssentialsConfig System { get; set; } + + /// + /// Gets or sets the Template + /// + public EssentialsConfig Template { get; set; } } -} - -/// -/// Represents a Package Version -/// -public class PackageVersion -{ - /// - /// Gets or sets the Version - /// - [JsonProperty("version")] - public string Version { get; set; } - - /// - /// Gets or sets the PackageId - /// - [JsonProperty("packageId", NullValueHandling = NullValueHandling.Ignore)] - public string PackageId { get; set; } - - /// - /// Gets or sets the RepoUrl - /// - [JsonProperty("repoUrl", NullValueHandling = NullValueHandling.Ignore)] - public string RepoUrl { get; set; } - - /// - /// Gets or sets the human-readable name - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } -} - -/// -/// Represents the configuration for a system and its template -/// -public class SystemTemplateConfigs -{ - /// - /// Gets or sets the System configuration - /// - public EssentialsConfig System { get; set; } - - /// - /// Gets or sets the Template - /// - public EssentialsConfig Template { get; set; } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.schema.json b/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.schema.json deleted file mode 100644 index dd7d1d11..00000000 --- a/src/PepperDash.Essentials.Core/Config/Essentials/EssentialsConfig.schema.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://pepperdash.com/schemas/Essentials/EssentialsConfig.schema.json", - "title": "EssentialsConfig", - "description": "Root Essentials configuration file, backed by EssentialsConfig (EssentialsConfig.cs), which extends BasicConfig (BasicConfig.cs).", - "type": "object", - "properties": { - "info": { - "$ref": "#/$defs/infoConfig" - }, - "devices": { - "type": "array", - "description": "The list of devices (including plugin devices and rooms) to instantiate. Each plugin publishes its own schema for the shape of its \"properties\" object.", - "items": { - "$ref": "#/$defs/deviceConfigEntry" - } - }, - "sourceLists": { - "type": "object", - "description": "Named lists of source list items, keyed by list name then item key.", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/sourceListItem" - } - } - }, - "destinationLists": { - "type": "object", - "description": "Named lists of destination list items, keyed by list name then item key.", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/destinationListItem" - } - } - }, - "audioControlPointLists": { - "type": "object", - "description": "Named audio control point lists, keyed by list name.", - "additionalProperties": { - "$ref": "#/$defs/audioControlPointListItem" - } - }, - "cameraLists": { - "type": "object", - "description": "Named lists of camera list items, keyed by list name then item key.", - "additionalProperties": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/cameraListItem" - } - } - }, - "tieLines": { - "type": "array", - "description": "Static tie lines between routing ports.", - "items": { - "$ref": "#/$defs/tieLineConfig" - } - }, - "joinMaps": { - "type": "object", - "description": "Join map override data, keyed by device/room key. Structure is join-map-specific and not further constrained here.", - "additionalProperties": { - "type": "object" - } - }, - "system_url": { - "type": "string", - "description": "The System URL used to derive systemUuid." - }, - "template_url": { - "type": "string", - "description": "The Template URL used to derive templateUuid." - }, - "systemUuid": { - "type": "string", - "readOnly": true, - "description": "Computed from system_url. Not intended to be authored directly." - }, - "templateUuid": { - "type": "string", - "readOnly": true, - "description": "Computed from template_url. Not intended to be authored directly." - }, - "rooms": { - "type": "array", - "description": "The list of room devices for this system, using the same device-entry shape as \"devices\".", - "items": { - "$ref": "#/$defs/deviceConfigEntry" - } - }, - "versions": { - "$ref": "#/$defs/versionData" - } - }, - "required": [ - "info", - "devices" - ], - "$defs": { - "deviceConfigEntry": { - "type": "object", - "description": "Generic device config entry (DeviceConfig.cs). Each plugin defines its own schema for the shape of \"properties\" for a given \"type\".", - "properties": { - "key": { - "type": "string", - "description": "Unique identifier for this device within the configuration file." - }, - "name": { - "type": "string", - "description": "Friendly display name for this device." - }, - "type": { - "type": "string", - "description": "Discriminator identifying the plugin factory that instantiates this device." - }, - "properties": { - "type": "object", - "description": "Type-specific properties object; see the owning plugin's schema for its shape." - } - }, - "required": [ - "key", - "name", - "type" - ] - }, - "infoConfig": { - "type": "object", - "description": "The info section of a config file (InfoConfig.cs).", - "properties": { - "name": { - "type": "string" - }, - "date": { - "type": "string", - "format": "date-time" - }, - "type": { - "type": "string" - }, - "version": { - "type": "string" - }, - "runtimeInfo": { - "$ref": "#/$defs/runtimeInfo" - }, - "comment": { - "type": "string" - }, - "hostname": { - "type": "string", - "readOnly": true, - "description": "Populated from the processor at runtime." - }, - "appNumber": { - "type": "integer", - "readOnly": true, - "description": "Populated from the processor at runtime." - } - } - }, - "runtimeInfo": { - "type": "object", - "description": "Runtime information about the program/processor (InfoConfig.cs). Populated at runtime; not intended to be authored.", - "readOnly": true, - "properties": { - "appName": { - "type": "string" - }, - "assemblyVersion": { - "type": "string" - }, - "osVersion": { - "type": "string" - }, - "ipInfo": { - "type": "object", - "additionalProperties": true - } - } - }, - "versionData": { - "type": "object", - "description": "Version data for Essentials and its packages (VersionData in EssentialsConfig.cs).", - "properties": { - "essentials": { - "$ref": "#/$defs/packageVersion" - }, - "userInterfaces": { - "type": "array", - "items": { - "$ref": "#/$defs/packageVersion" - } - }, - "touchpanelWrapperApp": { - "$ref": "#/$defs/packageVersion" - }, - "packages": { - "type": "array", - "items": { - "$ref": "#/$defs/packageVersion" - } - } - } - }, - "packageVersion": { - "type": "object", - "description": "A named package/version pair (PackageVersion in EssentialsConfig.cs).", - "properties": { - "version": { - "type": "string" - }, - "packageId": { - "type": "string" - }, - "repoUrl": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "version" - ] - }, - "sourceListItem": { - "type": "object", - "description": "An item in a source list (SourceListItem.cs).", - "properties": { - "sourceKey": { - "type": "string", - "description": "Key of the source device in the DeviceManager." - }, - "preferredName": { - "type": "string", - "readOnly": true, - "description": "Computed from name or the source device's name." - }, - "name": { - "type": "string", - "description": "A name that will override the source's name on the UI." - }, - "icon": { - "type": "string" - }, - "altIcon": { - "type": "string" - }, - "includeInSourceList": { - "type": "boolean" - }, - "order": { - "type": "integer" - }, - "volumeControlKey": { - "type": "string", - "description": "The key of the device for volume control." - }, - "type": { - "type": "string", - "pattern": "^(?:[rR][oO][uU][tT][eE]|[oO][fF][fF]|[oO][tT][hH][eE][rR])(?:\\s*,\\s*(?:[rR][oO][uU][tT][eE]|[oO][fF][fF]|[oO][tT][hH][eE][rR]))*$", - "description": "Case-insensitive." - }, - "routeList": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceRouteListItem" - } - }, - "disableCodecSharing": { - "type": "boolean", - "description": "Disable sharing this source to far-end call participants via codec content." - } - } - }, - "sourceRouteListItem": { - "type": "object", - "description": "A route to execute for a source list item (SourceRouteListItem in SourceListItem.cs).", - "properties": { - "sourceKey": { - "type": "string" - }, - "sourcePortKey": { - "type": "string" - }, - "destinationKey": { - "type": "string" - }, - "destinationPortKey": { - "type": "string" - }, - "type": { - "type": "string", - "description": "Signal type being routed. Flags enum; combinations may be comma-separated. Case-insensitive.", - "pattern": "^(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB])(?:\\s*,\\s*(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB]))*$" - }, - "destinationListItemKey": { - "type": "string", - "description": "If set together with sourceListItemKey, the direct route method is used." - }, - "sourceListItemKey": { - "type": "string", - "description": "If set together with destinationListItemKey, the direct route method is used." - } - } - }, - "destinationListItem": { - "type": "object", - "description": "A destination item in a routing system (DestinationListItem.cs).", - "properties": { - "sinkKey": { - "type": "string", - "description": "Key of the sink device that this destination represents." - }, - "preferredName": { - "type": "string", - "readOnly": true, - "description": "Computed from name or the sink device's name." - }, - "name": { - "type": "string" - }, - "includeInDestinationList": { - "type": "boolean" - }, - "order": { - "type": "integer" - }, - "surfaceLocation": { - "type": "integer" - }, - "verticalLocation": { - "type": "integer" - }, - "horizontalLocation": { - "type": "integer" - }, - "sinkType": { - "type": "string", - "description": "Signal type this destination can receive. Flags enum; combinations may be comma-separated. Case-insensitive.", - "pattern": "^(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB])(?:\\s*,\\s*(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB]))*$" - }, - "isCodecContentDestination": { - "type": "boolean" - }, - "isProgramAudioDestination": { - "type": "boolean" - }, - "supportsUsb": { - "type": "boolean" - }, - "destinationPortKey": { - "type": "string" - } - } - }, - "cameraListItem": { - "type": "object", - "description": "An item in a camera list (CameraListItem.cs).", - "properties": { - "deviceKey": { - "type": "string", - "description": "Key of the camera device in the DeviceManager." - }, - "preferredName": { - "type": "string", - "readOnly": true, - "description": "Computed from name or the camera device's name." - }, - "name": { - "type": "string" - }, - "icon": { - "type": "string" - }, - "altIcon": { - "type": "string" - }, - "includeInUserList": { - "type": "boolean" - }, - "order": { - "type": "integer" - } - } - }, - "audioControlPointListItem": { - "type": "object", - "description": "A named audio control point list containing level controls and presets (AudioControlPointListItem.cs).", - "properties": { - "levelControls": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/levelControlListItem" - } - }, - "presets": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/presetListItem" - } - } - } - }, - "audioControlListItemBase": { - "type": "object", - "description": "Common fields shared by level control and preset list items (AudioControlListItemBase.cs).", - "properties": { - "parentDeviceKey": { - "type": "string", - "description": "Key of the parent device in the DeviceManager." - }, - "itemKey": { - "type": "string", - "description": "Optional key of the item within the parent device." - }, - "name": { - "type": "string" - }, - "includeInUserList": { - "type": "boolean" - }, - "order": { - "type": "integer" - } - } - }, - "levelControlListItem": { - "description": "A level/mute control item in an audio control point list (LevelControlListItem.cs).", - "allOf": [ - { - "$ref": "#/$defs/audioControlListItemBase" - }, - { - "type": "object", - "properties": { - "preferredName": { - "type": "string", - "readOnly": true, - "description": "Computed from name or the underlying device's name." - }, - "deviceKey": { - "type": "string", - "readOnly": true, - "description": "Computed from parentDeviceKey/itemKey; not intended to be authored." - }, - "type": { - "type": "string", - "description": "Flags enum indicating whether this item is a level control, mute control, or both. Case-insensitive.", - "pattern": "^(?:[lL][eE][vV][eE][lL]|[mM][uU][tT][eE]|[lL][eE][vV][eE][lL][aA][nN][dD][mM][uU][tT][eE])(?:\\s*,\\s*(?:[lL][eE][vV][eE][lL]|[mM][uU][tT][eE]|[lL][eE][vV][eE][lL][aA][nN][dD][mM][uU][tT][eE]))*$" - }, - "isMic": { - "type": "boolean", - "description": "Indicates if this item represents a microphone." - } - } - } - ] - }, - "presetListItem": { - "description": "A preset item in an audio control point list (PresetListItem.cs).", - "allOf": [ - { - "$ref": "#/$defs/audioControlListItemBase" - }, - { - "type": "object", - "properties": { - "preferredName": { - "type": "string", - "readOnly": true, - "description": "Computed from name or the underlying preset's name." - } - } - } - ] - }, - "tieLineConfig": { - "type": "object", - "description": "A static tie line between two routing ports (TieLineConfig.cs).", - "properties": { - "SourceKey": { - "type": "string" - }, - "SourceCard": { - "type": "string", - "description": "Key of the source card, if applicable (e.g. in a modular chassis)." - }, - "SourcePort": { - "type": "string" - }, - "DestinationKey": { - "type": "string" - }, - "DestinationCard": { - "type": "string" - }, - "DestinationPort": { - "type": "string" - }, - "type": { - "type": "string", - "description": "Optional override for the signal type of the tie line. Flags enum; combinations may be comma-separated. Case-insensitive.", - "pattern": "^(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB])(?:\\s*,\\s*(?:[aA][uU][dD][iI][oO]|[vV][iI][dD][eE][oO]|[aA][uU][dD][iI][oO][vV][iI][dD][eE][oO]|[uU][sS][bB]))*$" - } - }, - "required": [ - "SourceKey", - "SourcePort", - "DestinationKey", - "DestinationPort" - ] - } - } -} diff --git a/src/PepperDash.Essentials.Core/Config/ILoadConfig.cs b/src/PepperDash.Essentials.Core/Config/ILoadConfig.cs index 8c093176..ed6ff61f 100644 --- a/src/PepperDash.Essentials.Core/Config/ILoadConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/ILoadConfig.cs @@ -4,15 +4,16 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for loading configuration. This is called after all plugins have been loaded, but before any devices are initialized. This allows for any necessary configuration merging or processing to be done before devices are created and initialized. -/// -public interface ILoadConfig +namespace PepperDash.Essentials.Core { /// - /// Executes the loading of the configuration. This is called after all plugins have been loaded, but before any devices are initialized. This allows for any necessary configuration merging or processing to be done before devices are created and initialized. + /// Defines the contract for ILoadConfig /// - void GoWithLoad(); + public interface ILoadConfig + { + /// + /// GoWithLoad method + /// + void GoWithLoad(); + } } diff --git a/src/PepperDash.Essentials.Core/Config/InfoConfig.cs b/src/PepperDash.Essentials.Core/Config/InfoConfig.cs index 7c2f81e4..1ce2c60b 100644 --- a/src/PepperDash.Essentials.Core/Config/InfoConfig.cs +++ b/src/PepperDash.Essentials.Core/Config/InfoConfig.cs @@ -5,12 +5,12 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core.Config; - -/// -/// Represents the info section of a Config file -/// -public class InfoConfig +namespace PepperDash.Essentials.Core.Config +{ + /// + /// Represents the info section of a Config file + /// + public class InfoConfig { /// /// Gets or sets the Name @@ -70,49 +70,49 @@ public class InfoConfig Type = ""; Version = ""; Comment = ""; - HostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); - AppNumber = InitialParametersClass.ApplicationNumber; + HostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + AppNumber = InitialParametersClass.ApplicationNumber; - RuntimeInfo = new RuntimeInfo(); + RuntimeInfo = new RuntimeInfo(); } } - -/// -/// Represents runtime information about the program/processor -/// -public class RuntimeInfo -{ + /// - /// The name of the running application + /// Represents a RuntimeInfo /// - [JsonProperty("appName")] - public string AppName {get; set;} - //{ - // get - // { - // return Assembly.GetExecutingAssembly().GetName().Name; - // } - //} + public class RuntimeInfo + { + /// + /// The name of the running application + /// + [JsonProperty("appName")] + public string AppName {get; set;} + //{ + // get + // { + // return Assembly.GetExecutingAssembly().GetName().Name; + // } + //} - /// - /// The Assembly version of the running application - /// - [JsonProperty("assemblyVersion")] - public string AssemblyVersion {get; set;} - //{ - // get - // { - // var version = Assembly.GetExecutingAssembly().GetName().Version; - // return string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); - // } - //} + /// + /// The Assembly version of the running application + /// + [JsonProperty("assemblyVersion")] + public string AssemblyVersion {get; set;} + //{ + // get + // { + // var version = Assembly.GetExecutingAssembly().GetName().Version; + // return string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); + // } + //} - /// , - /// The OS Version of the processor (Firmware Version) - /// - [JsonProperty("osVersion")] - public string OsVersion {get; set;} + /// , + /// The OS Version of the processor (Firmware Version) + /// + [JsonProperty("osVersion")] + public string OsVersion {get; set;} //{ // get // { @@ -120,15 +120,17 @@ public class RuntimeInfo // } //} - /// - /// The information gathered by the processor at runtime about it's NICs and their IP addresses. - /// - [JsonProperty("ipInfo")] - public Dictionary IpInfo - { - get + /// + /// The information gathered by the processor at runtime about it's NICs and their IP addresses. + /// + [JsonProperty("ipInfo")] + public Dictionary IpInfo { - return Global.EthernetAdapterInfoCollection; + get + { + return Global.EthernetAdapterInfoCollection; + } } } + } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Config/SourceDevicePropertiesConfigBase.cs b/src/PepperDash.Essentials.Core/Config/SourceDevicePropertiesConfigBase.cs index ae30de0a..42a576a2 100644 --- a/src/PepperDash.Essentials.Core/Config/SourceDevicePropertiesConfigBase.cs +++ b/src/PepperDash.Essentials.Core/Config/SourceDevicePropertiesConfigBase.cs @@ -4,12 +4,16 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.Config; - -public class SourceDevicePropertiesConfigBase +namespace PepperDash.Essentials.Core.Config { - /// - /// Gets or sets the DisableSharing - /// - public bool DisableSharing { get; set; } + /// + /// Represents a SourceDevicePropertiesConfigBase + /// + public class SourceDevicePropertiesConfigBase + { + /// + /// Gets or sets the DisableSharing + /// + public bool DisableSharing { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs b/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs index a8547ba3..bdb3e461 100644 --- a/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs +++ b/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs @@ -7,8 +7,11 @@ using PepperDash.Core.JsonStandardObjects; using PepperDash.Essentials.Core.Bridges; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ + /// + /// Abstract base class for Crestron GenericBase devices + /// public abstract class CrestronGenericBaseDevice : EssentialsDevice, IOnline, IHasFeedback, ICommunicationMonitor, IUsageTracking { /// @@ -16,10 +19,10 @@ namespace PepperDash.Essentials.Core; /// protected GenericBase Hardware; - /// - /// Returns a list containing the Outputs that we want to expose. - /// - public FeedbackCollection Feedbacks { get; private set; } + /// + /// Gets or sets the Feedbacks + /// + public FeedbackCollection Feedbacks { get; private set; } /// /// Gets or sets the IsOnline @@ -48,25 +51,30 @@ namespace PepperDash.Essentials.Core; /// name of the device /// hardware instance protected CrestronGenericBaseDevice(string key, string name, GenericBase hardware) - : base(key, name) - { - Feedbacks = new FeedbackCollection(); + : base(key, name) + { + Feedbacks = new FeedbackCollection(); - Hardware = hardware; - IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline); - IsRegistered = new BoolFeedback("IsRegistered", () => Hardware.Registered); - IpConnectionsText = new StringFeedback("IpConnectionsText", () => Hardware.ConnectedIpList != null ? string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray()) : string.Empty); - AddToFeedbackList(IsOnline, IpConnectionsText); + Hardware = hardware; + IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline); + IsRegistered = new BoolFeedback("IsRegistered", () => Hardware.Registered); + IpConnectionsText = new StringFeedback("IpConnectionsText", () => Hardware.ConnectedIpList != null ? string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray()) : string.Empty); + AddToFeedbackList(IsOnline, IpConnectionsText); - CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000); - } + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000); + } - protected CrestronGenericBaseDevice(string key, string name) - : base(key, name) - { - Feedbacks = new FeedbackCollection(); + /// + /// Constructor without hardware instance + /// + /// key of the device + /// name of the device + protected CrestronGenericBaseDevice(string key, string name) + : base(key, name) + { + Feedbacks = new FeedbackCollection(); - } + } /// /// Registers the Crestron GenericBase hardware instance @@ -74,58 +82,58 @@ namespace PepperDash.Essentials.Core; /// hardware instance protected void RegisterCrestronGenericBase(GenericBase hardware) { - Hardware = hardware; - IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline); - IsRegistered = new BoolFeedback("IsRegistered", () => Hardware.Registered); - IpConnectionsText = new StringFeedback("IpConnectionsText", () => Hardware.ConnectedIpList != null ? string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray()) : string.Empty); - AddToFeedbackList(IsOnline, IpConnectionsText); + Hardware = hardware; + IsOnline = new BoolFeedback("IsOnlineFeedback", () => Hardware.IsOnline); + IsRegistered = new BoolFeedback("IsRegistered", () => Hardware.Registered); + IpConnectionsText = new StringFeedback("IpConnectionsText", () => Hardware.ConnectedIpList != null ? string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray()) : string.Empty); + AddToFeedbackList(IsOnline, IpConnectionsText); - CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000); + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000); } /// /// CustomActivate method /// /// - protected override bool CustomActivate() + public override bool CustomActivate() { - Debug.LogMessage(LogEventLevel.Information, this, "Activating"); - if (!PreventRegistration) - { - //Debug.LogMessage(LogEventLevel.Debug, this, " Does not require registration. Skipping"); - - if (Hardware.Registerable && !Hardware.Registered) + Debug.LogMessage(LogEventLevel.Information, this, "Activating"); + if (!PreventRegistration) { - var response = Hardware.RegisterWithLogging(Key); - if (response != eDeviceRegistrationUnRegistrationResponse.Success) + //Debug.LogMessage(LogEventLevel.Debug, this, " Does not require registration. Skipping"); + + if (Hardware.Registerable && !Hardware.Registered) { - //Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response); - return false; + var response = Hardware.RegisterWithLogging(Key); + if (response != eDeviceRegistrationUnRegistrationResponse.Success) + { + //Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response); + return false; + } } + + IsRegistered.FireUpdate(); + } + else + { + AddPostActivationAction(() => + { + if (Hardware.Registerable && !Hardware.Registered) + { + var response = Hardware.RegisterWithLogging(Key); + } + + IsRegistered.FireUpdate(); + }); } - IsRegistered.FireUpdate(); - } - else - { - AddPostActivationAction(() => - { - if (Hardware.Registerable && !Hardware.Registered) - { - var response = Hardware.RegisterWithLogging(Key); - } + foreach (var f in Feedbacks) + { + f.FireUpdate(); + } - IsRegistered.FireUpdate(); - }); - } - - foreach (var f in Feedbacks) - { - f.FireUpdate(); - } - - Hardware.OnlineStatusChange += Hardware_OnlineStatusChange; - CommunicationMonitor.Start(); + Hardware.OnlineStatusChange += Hardware_OnlineStatusChange; + CommunicationMonitor.Start(); return base.CustomActivate(); } @@ -141,42 +149,42 @@ namespace PepperDash.Essentials.Core; var success = Hardware.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success; - IsRegistered.FireUpdate(); + IsRegistered.FireUpdate(); - return success; + return success; } /// - /// Adds feedback(s) to the list - /// - /// - public void AddToFeedbackList(params Feedback[] newFbs) - { - foreach (var f in newFbs) + /// Adds feedback(s) to the list + /// + /// feedback(s) to be added to the list + public void AddToFeedbackList(params Feedback[] newFbs) { - if (f == null) continue; - - if (!Feedbacks.Contains(f)) + foreach (var f in newFbs) { - Feedbacks.Add(f); + if (f == null) continue; + + if (!Feedbacks.Contains(f)) + { + Feedbacks.Add(f); + } } } - } void Hardware_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) { - Debug.LogMessage(LogEventLevel.Verbose, this, "OnlineStatusChange Event. Online = {0}", args.DeviceOnLine); + Debug.LogMessage(LogEventLevel.Verbose, this, "OnlineStatusChange Event. Online = {0}", args.DeviceOnLine); - if (!Hardware.Registered) - { - return; // protects in cases where device has been unregistered and feedbacks would attempt to access null sigs. - } + if (!Hardware.Registered) + { + return; // protects in cases where device has been unregistered and feedbacks would attempt to access null sigs. + } - foreach (var feedback in Feedbacks) - { - if (feedback != null) - feedback.FireUpdate(); - } + foreach (var feedback in Feedbacks) + { + if (feedback != null) + feedback.FireUpdate(); + } } #region IStatusMonitor Members @@ -187,28 +195,52 @@ namespace PepperDash.Essentials.Core; public StatusMonitorBase CommunicationMonitor { get; private set; } #endregion - #region IUsageTracking Members + #region IUsageTracking Members - public UsageTracking UsageTracker { get; set; } + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } - #endregion + #endregion } -public abstract class CrestronGenericBridgeableBaseDevice : CrestronGenericBaseDevice, IBridgeAdvanced -{ - protected CrestronGenericBridgeableBaseDevice(string key, string name, GenericBase hardware) : base(key, name, hardware) + /// + /// Abstract base class for Crestron GenericBase devices that are bridgeable + /// + public abstract class CrestronGenericBridgeableBaseDevice : CrestronGenericBaseDevice, IBridgeAdvanced { + + /// + /// Constructor + /// + /// key of the device + /// name of the device + /// hardware instance + protected CrestronGenericBridgeableBaseDevice(string key, string name, GenericBase hardware) : base(key, name, hardware) + { + } + + /// + /// Constructor without hardware instance + /// + /// key of the device + /// name of the device + protected CrestronGenericBridgeableBaseDevice(string key, string name) + : base(key, name) + { + } + + /// + /// Links to API + /// + /// the trilist + /// the starting join number + /// the join map key + /// the bridge instance + public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); } - protected CrestronGenericBridgeableBaseDevice(string key, string name) - : base(key, name) - { - } - - - public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); -} - //*********************************************************************************** /// @@ -249,4 +281,5 @@ public abstract class CrestronGenericBridgeableBaseDevice : CrestronGenericBaseD return result; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs.orig b/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs.orig new file mode 100644 index 00000000..bef4ae01 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Crestron/CrestronGenericBaseDevice.cs.orig @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; + +using PepperDash.Core; + +namespace PepperDash.Essentials.Core +{ + /// + /// A bridge class to cover the basic features of GenericBase hardware + /// + public class CrestronGenericBaseDevice : Device, IOnline, IHasFeedback, ICommunicationMonitor, IUsageTracking + { + public virtual GenericBase Hardware { get; protected set; } + + public BoolFeedback IsOnline { get; private set; } + public BoolFeedback IsRegistered { get; private set; } + public StringFeedback IpConnectionsText { get; private set; } + + public CrestronGenericBaseDevice(string key, string name, GenericBase hardware) + : base(key, name) + { + Hardware = hardware; + IsOnline = new BoolFeedback(CommonBoolCue.IsOnlineFeedback, () => Hardware.IsOnline); + IsRegistered = new BoolFeedback(new Cue("IsRegistered", 0, eCueType.Bool), () => Hardware.Registered); + IpConnectionsText = new StringFeedback(CommonStringCue.IpConnectionsText, () => + string.Join(",", Hardware.ConnectedIpList.Select(cip => cip.DeviceIpAddress).ToArray())); + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, hardware, 120000, 300000); + } + + /// + /// Make sure that overriding classes call this! + /// Registers the Crestron device, connects up to the base events, starts communication monitor + /// + public override bool CustomActivate() + { + Debug.LogMessage(LogEventLevel.Information, this, "Activating"); + var response = Hardware.RegisterWithLogging(Key); + if (response != eDeviceRegistrationUnRegistrationResponse.Success) + { +<<<<<<< HEAD + Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response); + return false; + } +======= + Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Cannot register Crestron device: {0}", response); + return false; + } +>>>>>>> origin/feature/ecs-342-neil + Hardware.OnlineStatusChange += new OnlineStatusChangeEventHandler(Hardware_OnlineStatusChange); + CommunicationMonitor.Start(); + + return true; + } + + /// + /// This disconnects events and unregisters the base hardware device. + /// + /// + public override bool Deactivate() + { + CommunicationMonitor.Stop(); + Hardware.OnlineStatusChange -= Hardware_OnlineStatusChange; + + return Hardware.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success; + } + + /// + /// Returns a list containing the Outputs that we want to expose. + /// + public virtual List Feedbacks + { + get + { + return new List + { + IsOnline, + IsRegistered, + IpConnectionsText + }; + } + } + + void Hardware_OnlineStatusChange(GenericBase currentDevice, OnlineOfflineEventArgs args) + { + IsOnline.FireUpdate(); + } + + #region IStatusMonitor Members + + public StatusMonitorBase CommunicationMonitor { get; private set; } + #endregion + + #region IUsageTracking Members + + public UsageTracking UsageTracker { get; set; } + + #endregion + } + + //*********************************************************************************** + public class CrestronGenericBaseDeviceEventIds + { + public const uint IsOnline = 1; + public const uint IpConnectionsText =2; + } + + /// + /// Adds logging to Register() failure + /// + public static class GenericBaseExtensions + { + public static eDeviceRegistrationUnRegistrationResponse RegisterWithLogging(this GenericBase device, string key) + { + var result = device.Register(); + if (result != eDeviceRegistrationUnRegistrationResponse.Success) + { + Debug.LogMessage(LogEventLevel.Information, "Cannot register device '{0}': {1}", key, result); + } + return result; + } + + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/CrestronIO/GenericRelayDevice.cs b/src/PepperDash.Essentials.Core/CrestronIO/GenericRelayDevice.cs index 15dfcd36..a41e1411 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/GenericRelayDevice.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/GenericRelayDevice.cs @@ -13,209 +13,253 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core.CrestronIO; - -/// -/// Represents a generic device controlled by relays -/// -[Description("Wrapper class for a Relay")] -public class GenericRelayDevice : EssentialsBridgeableDevice, ISwitchedOutput +namespace PepperDash.Essentials.Core.CrestronIO { - public Relay RelayOutput { get; private set; } - - public BoolFeedback OutputIsOnFeedback { get; private set; } - - //Maintained for compatibility with PepperDash.Essentials.Core.Devices.CrestronProcessor - public GenericRelayDevice(string key, Relay relay) : - base(key) + /// + /// Represents a generic device controlled by relays + /// + [Description("Wrapper class for a Relay")] + public class GenericRelayDevice : EssentialsBridgeableDevice, ISwitchedOutput { - OutputIsOnFeedback = new BoolFeedback(new Func(() => RelayOutput.State)); + /// + /// The RelayOutput controlled by this device + /// + public Relay RelayOutput { get; private set; } - RelayOutput = relay; - RelayOutput.Register(); + /// + /// Feedback to indicate whether the output is on + /// + public BoolFeedback OutputIsOnFeedback { get; private set; } - RelayOutput.StateChange += RelayOutput_StateChange; - } - - public GenericRelayDevice(string key, string name, Func postActivationFunc, - IOPortConfig config) - : base(key, name) - { - OutputIsOnFeedback = new BoolFeedback(() => RelayOutput.State); - - AddPostActivationAction(() => + //Maintained for compatibility with PepperDash.Essentials.Core.Devices.CrestronProcessor + /// + /// Constructor for GenericRelayDevice + /// + /// key of the device + /// Relay output controlled by this device + public GenericRelayDevice(string key, Relay relay) : + base(key) { - RelayOutput = postActivationFunc(config); - - if (RelayOutput == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to get parent relay device for device key {0} and port {1}", config.PortDeviceKey, config.PortNumber); - return; - } + OutputIsOnFeedback = new BoolFeedback(new Func(() => RelayOutput.State)); + RelayOutput = relay; RelayOutput.Register(); RelayOutput.StateChange += RelayOutput_StateChange; - }); - } + } - #region PreActivate - - private static Relay GetRelay(IOPortConfig dc) - { - - IRelayPorts relayDevice; - - if(dc.PortDeviceKey.Equals("processor")) + /// + /// Constructor for GenericRelayDevice + /// + /// key of the device + /// name of the device + /// function to get the relay output + /// IO port configuration + public GenericRelayDevice(string key, string name, Func postActivationFunc, + IOPortConfig config) + : base(key, name) { - if (!Global.ControlSystem.SupportsRelay) + OutputIsOnFeedback = new BoolFeedback(() => RelayOutput.State); + + AddPostActivationAction(() => { - Debug.LogMessage(LogEventLevel.Information, "Processor does not support relays"); + RelayOutput = postActivationFunc(config); + + if (RelayOutput == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "Unable to get parent relay device for device key {0} and port {1}", config.PortDeviceKey, config.PortNumber); + return; + } + + RelayOutput.Register(); + + RelayOutput.StateChange += RelayOutput_StateChange; + }); + } + + #region PreActivate + + private static Relay GetRelay(IOPortConfig dc) + { + + IRelayPorts relayDevice; + + if(dc.PortDeviceKey.Equals("processor")) + { + if (!Global.ControlSystem.SupportsRelay) + { + Debug.LogMessage(LogEventLevel.Information, "Processor does not support relays"); + return null; + } + relayDevice = Global.ControlSystem; + + return relayDevice.RelayPorts[dc.PortNumber]; + } + + var essentialsDevice = DeviceManager.GetDeviceForKey(dc.PortDeviceKey); + if (essentialsDevice == null) + { + Debug.LogMessage(LogEventLevel.Information, "Device {0} was not found in Device Manager. Check configuration or for errors with device.", dc.PortDeviceKey); return null; } - relayDevice = Global.ControlSystem; - return relayDevice.RelayPorts[dc.PortNumber]; - } - - var essentialsDevice = DeviceManager.GetDeviceForKey(dc.PortDeviceKey); - if (essentialsDevice == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device {0} was not found in Device Manager. Check configuration or for errors with device.", dc.PortDeviceKey); + relayDevice = essentialsDevice as IRelayPorts; + + if (relayDevice == null) + { + Debug.LogMessage(LogEventLevel.Information, "Device {0} is not a valid relay parent. Please check configuration.", dc.PortDeviceKey); + return null; + } + + if (dc.PortNumber <= relayDevice.NumberOfRelayPorts) + { + return relayDevice.RelayPorts[dc.PortNumber]; + } + + Debug.LogMessage(LogEventLevel.Information, "Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); return null; } - relayDevice = essentialsDevice as IRelayPorts; - - if (relayDevice == null) + #endregion + + #region Events + + void RelayOutput_StateChange(Relay relay, RelayEventArgs args) { - Debug.LogMessage(LogEventLevel.Information, "Device {0} is not a valid relay parent. Please check configuration.", dc.PortDeviceKey); - return null; + OutputIsOnFeedback.FireUpdate(); } - if (dc.PortNumber <= relayDevice.NumberOfRelayPorts) + #endregion + + #region Methods + + /// + /// OpenRelay method + /// + public void OpenRelay() { - return relayDevice.RelayPorts[dc.PortNumber]; + RelayOutput.State = false; } - Debug.LogMessage(LogEventLevel.Information, "Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); - return null; - } - - #endregion - - #region Events - - void RelayOutput_StateChange(Relay relay, RelayEventArgs args) - { - OutputIsOnFeedback.FireUpdate(); - } - - #endregion - - #region Methods - - public void OpenRelay() - { - RelayOutput.State = false; - } - - public void CloseRelay() - { - RelayOutput.State = true; - } - - public void ToggleRelayState() - { - if (RelayOutput.State == true) - OpenRelay(); - else - CloseRelay(); - } - - #endregion - - #region ISwitchedOutput Members - - void ISwitchedOutput.On() - { - CloseRelay(); - } - - void ISwitchedOutput.Off() - { - OpenRelay(); - } - - #endregion - - #region Bridge Linking - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new GenericRelayControllerJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) + /// + /// CloseRelay method + /// + public void CloseRelay() { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + RelayOutput.State = true; } - if (RelayOutput == null) + /// + /// ToggleRelayState method + /// + public void ToggleRelayState() { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to link device '{0}'. Relay is null", Key); - return; - } - - Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - - trilist.SetBoolSigAction(joinMap.Relay.JoinNumber, b => - { - if (b) - CloseRelay(); - else + if (RelayOutput.State == true) OpenRelay(); - }); - - // feedback for relay state - - OutputIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.Relay.JoinNumber]); - } - - #endregion - - #region Factory - - public class GenericRelayDeviceFactory : EssentialsDeviceFactory - { - public GenericRelayDeviceFactory() - { - TypeNames = new List() { "relayoutput" }; + else + CloseRelay(); } - public override EssentialsDevice BuildDevice(DeviceConfig dc) + #endregion + + #region ISwitchedOutput Members + + void ISwitchedOutput.On() { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Relay Device"); - - var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); - - if (props == null) return null; - - var portDevice = new GenericRelayDevice(dc.Key, dc.Name, GetRelay, props); - - return portDevice; + CloseRelay(); } - } - #endregion + void ISwitchedOutput.Off() + { + OpenRelay(); + } + + #endregion + + #region Bridge Linking + + /// + /// LinkToApi method + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new GenericRelayControllerJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + if (RelayOutput == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to link device '{0}'. Relay is null", Key); + return; + } + + Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + + trilist.SetBoolSigAction(joinMap.Relay.JoinNumber, b => + { + if (b) + CloseRelay(); + else + OpenRelay(); + }); + + // feedback for relay state + + OutputIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.Relay.JoinNumber]); + } + + #endregion + + #region Factory + + /// + /// Represents a GenericRelayDeviceFactory + /// + public class GenericRelayDeviceFactory : EssentialsDeviceFactory + { + /// + /// Constructor for GenericRelayDeviceFactory + /// + public GenericRelayDeviceFactory() + { + TypeNames = new List() { "relayoutput" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Relay Device"); + + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + + if (props == null) return null; + + var portDevice = new GenericRelayDevice(dc.Key, dc.Name, GetRelay, props); + + return portDevice; + } + } + + #endregion + + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs index d9deb961..ad5507d0 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs @@ -1,4 +1,6 @@ -using System; + + +using System; using System.Collections.Generic; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; diff --git a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs index 2088e952..47e8aef6 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs @@ -1,4 +1,6 @@ -using System; + + +using System; using System.Collections.Generic; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; diff --git a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs index 39c26cd6..ee3f0320 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs @@ -11,176 +11,173 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core.CrestronIO; - -/// -/// Represents a generic digital input deviced tied to a versiport -/// -public class GenericVersiportDigitalOutputDevice : EssentialsBridgeableDevice, IDigitalOutput +namespace PepperDash.Essentials.Core.CrestronIO { - public Versiport OutputPort { get; private set; } - - public BoolFeedback OutputStateFeedback { get; private set; } - - Func OutputStateFeedbackFunc + /// + /// Represents a generic digital input deviced tied to a versiport + /// + public class GenericVersiportDigitalOutputDevice : EssentialsBridgeableDevice, IDigitalOutput, IHasFeedback { - get + private Versiport outputPort; + + /// + /// Gets or sets the OutputStateFeedback + /// + public BoolFeedback OutputStateFeedback { get; private set; } + + /// + public FeedbackCollection Feedbacks { get; private set; } = new FeedbackCollection(); + + /// + /// Initializes a new instance of the class. + /// + public GenericVersiportDigitalOutputDevice(string key, string name, Func postActivationFunc, IOPortConfig config) : + base(key, name) { - return () => OutputPort.DigitalOut; - } - } + OutputStateFeedback = new BoolFeedback("outputState", () => outputPort.DigitalOut); - public GenericVersiportDigitalOutputDevice(string key, string name, Func postActivationFunc, IOPortConfig config) : - base(key, name) - { - OutputStateFeedback = new BoolFeedback(OutputStateFeedbackFunc); - - AddPostActivationAction(() => - { - OutputPort = postActivationFunc(config); - - OutputPort.Register(); - - - if (!OutputPort.SupportsDigitalOutput) + AddPostActivationAction(() => { - Debug.LogMessage(LogEventLevel.Information, this, "Device does not support configuration as a Digital Output"); + outputPort = postActivationFunc(config); + + outputPort.Register(); + + + if (!outputPort.SupportsDigitalOutput) + { + this.LogError("Device does not support configuration as a Digital Output"); + return; + } + + outputPort.SetVersiportConfiguration(eVersiportConfiguration.DigitalOutput); + + + outputPort.VersiportChange += OutputPort_VersiportChange; + + }); + } + + void OutputPort_VersiportChange(Versiport port, VersiportEventArgs args) + { + this.LogDebug("Versiport change: {event}", args.Event); + + if (args.Event == eVersiportEvent.DigitalOutChange) + OutputStateFeedback.FireUpdate(); + } + + /// + /// Set value of the versiport digital output + /// + /// value to set the output to + public void SetOutput(bool state) + { + if (!outputPort.SupportsDigitalOutput) + { + this.LogError("Versiport does not support Digital Output Mode"); return; } - OutputPort.SetVersiportConfiguration(eVersiportConfiguration.DigitalOutput); + outputPort.DigitalOut = state; + } + #region Bridge Linking - OutputPort.VersiportChange += OutputPort_VersiportChange; + /// + /// LinkToApi method + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new IDigitalOutputJoinMap(joinStart); - }); + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - } + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - void OutputPort_VersiportChange(Versiport port, VersiportEventArgs args) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Versiport change: {0}", args.Event); - - if(args.Event == eVersiportEvent.DigitalOutChange) - OutputStateFeedback.FireUpdate(); - } - - /// - /// Set value of the versiport digital output - /// - /// value to set the output to - public void SetOutput(bool state) - { - if (OutputPort.SupportsDigitalOutput) + if (bridge != null) { - Debug.LogMessage(LogEventLevel.Information, this, "Passed the Check"); - - OutputPort.DigitalOut = state; - + bridge.AddJoinMap(Key, joinMap); } else { - Debug.LogMessage(LogEventLevel.Information, this, "Versiport does not support Digital Output Mode"); + this.LogWarning("Please update config to use 'eiscapiadvanced' to get all join map features for this device."); } - } + try + { + this.LogDebug("Linking to Trilist '{0}'", trilist.ID.ToString("X")); - #region Bridge Linking - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new IDigitalOutputJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + // Link feedback for input state + OutputStateFeedback.LinkInputSig(trilist.BooleanInput[joinMap.OutputState.JoinNumber]); + trilist.SetBoolSigAction(joinMap.OutputState.JoinNumber, SetOutput); + } + catch (Exception e) + { + this.LogError("Unable to link device: {message}", e.Message); + this.LogDebug(e, "Stack Trace: "); + } } - try + #endregion + + + /// + /// GetVersiportDigitalOutput method + /// + public static Versiport GetVersiportDigitalOutput(IOPortConfig dc) { - Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - - // Link feedback for input state - OutputStateFeedback.LinkInputSig(trilist.BooleanInput[joinMap.OutputState.JoinNumber]); - trilist.SetBoolSigAction(joinMap.OutputState.JoinNumber, SetOutput); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to link device '{0}'. Input is null", Key); - Debug.LogMessage(LogEventLevel.Debug, this, "Error: {0}", e); - } - } - - #endregion - - - public static Versiport GetVersiportDigitalOutput(IOPortConfig dc) - { - - IIOPorts ioPortDevice; - if (dc.PortDeviceKey.Equals("processor")) { if (!Global.ControlSystem.SupportsVersiport) { - Debug.LogMessage(LogEventLevel.Information, "GetVersiportDigitalOuptut: Processor does not support Versiports"); + Debug.LogError("GetVersiportDigitalOutput: Processor does not support Versiports"); return null; } - ioPortDevice = Global.ControlSystem; + return Global.ControlSystem.VersiPorts[dc.PortNumber]; } - else + + if (!(DeviceManager.GetDeviceForKey(dc.PortDeviceKey) is IIOPorts ioPortDevice)) { - var ioPortDev = DeviceManager.GetDeviceForKey(dc.PortDeviceKey) as IIOPorts; - if (ioPortDev == null) - { - Debug.LogMessage(LogEventLevel.Information, "GetVersiportDigitalOuptut: Device {0} is not a valid device", dc.PortDeviceKey); - return null; - } - ioPortDevice = ioPortDev; - } - if (ioPortDevice == null) - { - Debug.LogMessage(LogEventLevel.Information, "GetVersiportDigitalOuptut: Device '0' is not a valid IOPorts Device", dc.PortDeviceKey); + Debug.LogError("GetVersiportDigitalOutput: Device {key} is not a valid device", dc.PortDeviceKey); return null; } if (dc.PortNumber > ioPortDevice.NumberOfVersiPorts) { - Debug.LogMessage(LogEventLevel.Information, "GetVersiportDigitalOuptut: Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); + Debug.LogMessage(LogEventLevel.Information, "GetVersiportDigitalOutput: Device {0} does not contain a port {1}", dc.PortDeviceKey, dc.PortNumber); + return null; } - var port = ioPortDevice.VersiPorts[dc.PortNumber]; - return port; - - } -} - - -public class GenericVersiportDigitalOutputDeviceFactory : EssentialsDeviceFactory -{ - public GenericVersiportDigitalOutputDeviceFactory() - { - TypeNames = new List() { "versiportoutput" }; + return ioPortDevice.VersiPorts[dc.PortNumber]; + } } - public override EssentialsDevice BuildDevice(DeviceConfig dc) + + /// + /// Represents a GenericVersiportDigitalOutputDeviceFactory + /// + public class GenericVersiportDigitalOutputDeviceFactory : EssentialsDeviceFactory { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Versiport Device"); + /// + /// Initialize a new instance of the class. + /// + public GenericVersiportDigitalOutputDeviceFactory() + { + TypeNames = new List() { "versiportoutput" }; + } - var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogDebug("Factory Attempting to create new Generic Versiport Device"); - if (props == null) return null; + var props = JsonConvert.DeserializeObject(dc.Properties.ToString()); - var portDevice = new GenericVersiportDigitalOutputDevice(dc.Key, dc.Name, GenericVersiportDigitalOutputDevice.GetVersiportDigitalOutput, props); + if (props == null) return null; - return portDevice; + var portDevice = new GenericVersiportDigitalOutputDevice(dc.Key, dc.Name, GenericVersiportDigitalOutputDevice.GetVersiportDigitalOutput, props); + + return portDevice; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/CrestronIO/IAnalogInput.cs b/src/PepperDash.Essentials.Core/CrestronIO/IAnalogInput.cs index c42e273c..426834ed 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/IAnalogInput.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/IAnalogInput.cs @@ -5,9 +5,7 @@ using System.Text; using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.CrestronIO; - -public interface IAnalogInput +namespace PepperDash.Essentials.Core.CrestronIO { /// /// Defines the contract for IAnalogInput diff --git a/src/PepperDash.Essentials.Core/CrestronIO/IDigitalInput.cs b/src/PepperDash.Essentials.Core/CrestronIO/IDigitalInput.cs index 963fa8b1..8e1b4c18 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/IDigitalInput.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/IDigitalInput.cs @@ -1,14 +1,19 @@ - -namespace PepperDash.Essentials.Core.CrestronIO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; - -/// -/// Represents a device that provides digital input -/// -public interface IDigitalInput +namespace PepperDash.Essentials.Core.CrestronIO { /// - /// Feedback to indicate the state of the input + /// Represents a device that provides digital input /// - BoolFeedback InputStateFeedback { get; } -} + public interface IDigitalInput + { + /// + /// Feedback to indicate the state of the input + /// + BoolFeedback InputStateFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/CrestronIO/IDigitalOutput.cs b/src/PepperDash.Essentials.Core/CrestronIO/IDigitalOutput.cs index 7c4f720a..82aa0686 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/IDigitalOutput.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/IDigitalOutput.cs @@ -4,12 +4,7 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.CrestronIO; - -/// -/// Represents a device that provides digital input -/// -public interface IDigitalOutput +namespace PepperDash.Essentials.Core.CrestronIO { /// /// Represents a device that provides digital input diff --git a/src/PepperDash.Essentials.Core/CrestronIO/IHasCresnetBranches.cs b/src/PepperDash.Essentials.Core/CrestronIO/IHasCresnetBranches.cs index 449d59f3..ee86b5fe 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/IHasCresnetBranches.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/IHasCresnetBranches.cs @@ -6,15 +6,16 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for IHasCresnetBranches -/// -public interface IHasCresnetBranches +namespace PepperDash.Essentials.Core { /// - /// Collection of Cresnet branches + /// Defines the contract for IHasCresnetBranches /// - CrestronCollection CresnetBranches { get; } -} + public interface IHasCresnetBranches + { + /// + /// Collection of Cresnet branches + /// + CrestronCollection CresnetBranches { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/CrestronIO/IOPortConfig.cs b/src/PepperDash.Essentials.Core/CrestronIO/IOPortConfig.cs index f89b3b0f..7b76958b 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/IOPortConfig.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/IOPortConfig.cs @@ -1,4 +1,5 @@  + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/PepperDash.Essentials.Core/CrestronIO/ISwitchedOutput.cs b/src/PepperDash.Essentials.Core/CrestronIO/ISwitchedOutput.cs index 9f5bec7b..30b4b8f9 100644 --- a/src/PepperDash.Essentials.Core/CrestronIO/ISwitchedOutput.cs +++ b/src/PepperDash.Essentials.Core/CrestronIO/ISwitchedOutput.cs @@ -6,36 +6,37 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.CrestronIO; - -/// -/// Describes an output capable of switching on and off -/// -public interface ISwitchedOutput +namespace PepperDash.Essentials.Core.CrestronIO { /// - /// Feedback to indicate the state of the output + /// Describes an output capable of switching on and off /// - BoolFeedback OutputIsOnFeedback {get;} + public interface ISwitchedOutput + { + /// + /// Feedback to indicate whether the output is on + /// + BoolFeedback OutputIsOnFeedback {get;} + + /// + /// Turns the output on + /// + void On(); + + /// + /// Turns the output off + /// + void Off(); + } /// - /// Turns the output on + /// Describes a collection of switched outputs /// - void On(); - - /// - /// Turns the output off - /// - void Off(); -} - -/// -/// Defines the contract for a class that has a collection of switched outputs -/// -public interface ISwitchedOutputCollection -{ - /// - /// Collection of switched outputs, indexed by their output number - /// - Dictionary SwitchedOutputs { get; } + public interface ISwitchedOutputCollection + { + /// + /// Dictionary of switched outputs by their port number + /// + Dictionary SwitchedOutputs { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Device Info/DeviceInfo.cs b/src/PepperDash.Essentials.Core/Device Info/DeviceInfo.cs index abd011ef..6e55c9b3 100644 --- a/src/PepperDash.Essentials.Core/Device Info/DeviceInfo.cs +++ b/src/PepperDash.Essentials.Core/Device Info/DeviceInfo.cs @@ -1,28 +1,29 @@ -namespace PepperDash.Essentials.Core.DeviceInfo; - -/// -/// Represents a DeviceInfo -/// -public class DeviceInfo +namespace PepperDash.Essentials.Core.DeviceInfo { /// - /// Gets or sets the HostName + /// Represents a DeviceInfo /// - public string HostName { get; set; } - /// - /// Gets or sets the IpAddress - /// - public string IpAddress { get; set; } - /// - /// Gets or sets the MacAddress - /// - public string MacAddress { get; set; } - /// - /// Gets or sets the SerialNumber - /// - public string SerialNumber { get; set; } - /// - /// Gets or sets the FirmwareVersion - /// - public string FirmwareVersion { get; set; } -} + public class DeviceInfo + { + /// + /// Gets or sets the HostName + /// + public string HostName { get; set; } + /// + /// Gets or sets the IpAddress + /// + public string IpAddress { get; set; } + /// + /// Gets or sets the MacAddress + /// + public string MacAddress { get; set; } + /// + /// Gets or sets the SerialNumber + /// + public string SerialNumber { get; set; } + /// + /// Gets or sets the FirmwareVersion + /// + public string FirmwareVersion { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Device Info/DeviceInfoEventArgs.cs b/src/PepperDash.Essentials.Core/Device Info/DeviceInfoEventArgs.cs index df4ea69d..266b0d0b 100644 --- a/src/PepperDash.Essentials.Core/Device Info/DeviceInfoEventArgs.cs +++ b/src/PepperDash.Essentials.Core/Device Info/DeviceInfoEventArgs.cs @@ -1,32 +1,32 @@ using System; -namespace PepperDash.Essentials.Core.DeviceInfo; - - -/// -/// Represents a DeviceInfoEventArgs -/// -public class DeviceInfoEventArgs : EventArgs +namespace PepperDash.Essentials.Core.DeviceInfo { /// - /// Gets or sets the DeviceInfo + /// Represents a DeviceInfoEventArgs /// - public DeviceInfo DeviceInfo { get; set; } - - /// - /// Constructor - /// - public DeviceInfoEventArgs() + public class DeviceInfoEventArgs:EventArgs { + /// + /// Gets or sets the DeviceInfo + /// + public DeviceInfo DeviceInfo { get; set; } - } + /// + /// Constructor + /// + public DeviceInfoEventArgs() + { + + } - /// - /// Constructor with DeviceInfo - /// - /// the DeviceInfo instance - public DeviceInfoEventArgs(DeviceInfo devInfo) - { - DeviceInfo = devInfo; + /// + /// Constructor with DeviceInfo + /// + /// the DeviceInfo instance + public DeviceInfoEventArgs(DeviceInfo devInfo) + { + DeviceInfo = devInfo; + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Device Info/IDeviceInfoProvider.cs b/src/PepperDash.Essentials.Core/Device Info/IDeviceInfoProvider.cs index d4dc0852..3f5a4735 100644 --- a/src/PepperDash.Essentials.Core/Device Info/IDeviceInfoProvider.cs +++ b/src/PepperDash.Essentials.Core/Device Info/IDeviceInfoProvider.cs @@ -1,31 +1,31 @@ using System; using PepperDash.Core; -namespace PepperDash.Essentials.Core.DeviceInfo; - -/// -/// Defines the contract for IDeviceInfoProvider -/// -public interface IDeviceInfoProvider : IKeyed +namespace PepperDash.Essentials.Core.DeviceInfo { /// - /// Gets the DeviceInfo + /// Defines the contract for IDeviceInfoProvider /// - DeviceInfo DeviceInfo { get; } + public interface IDeviceInfoProvider:IKeyed + { + /// + /// Gets the DeviceInfo + /// + DeviceInfo DeviceInfo { get; } + + /// + /// Event fired when DeviceInfo changes + /// + event DeviceInfoChangeHandler DeviceInfoChanged; + + /// + /// Updates the DeviceInfo + /// + void UpdateDeviceInfo(); + } /// - /// Event fired when DeviceInfo changes + /// Delegate for DeviceInfoChangeHandler /// - event DeviceInfoChangeHandler DeviceInfoChanged; - - /// - /// Updates the DeviceInfo - /// - void UpdateDeviceInfo(); -} - -/// -/// Delegate for DeviceInfoChangeHandler -/// -public delegate void DeviceInfoChangeHandler(IKeyed device, DeviceInfoEventArgs args); - + public delegate void DeviceInfoChangeHandler(IKeyed device, DeviceInfoEventArgs args); +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Device Info/NetworkDeviceHelpers.cs b/src/PepperDash.Essentials.Core/Device Info/NetworkDeviceHelpers.cs index 34557159..79eaa018 100644 --- a/src/PepperDash.Essentials.Core/Device Info/NetworkDeviceHelpers.cs +++ b/src/PepperDash.Essentials.Core/Device Info/NetworkDeviceHelpers.cs @@ -3,46 +3,46 @@ using System.Collections.Generic; using System.Linq; using PepperDash.Core; using Crestron.SimplSharp; +using PepperDash.Essentials.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.DeviceInfo; - -/// -/// Helper methods for network devices -/// -public static class NetworkDeviceHelpers +namespace PepperDash.Essentials.Core.DeviceInfo { /// - /// Event raised when ArpTable changes + /// Static class NetworkDeviceHelpers /// - public static event ArpTableEventHandler ArpTableUpdated; - - /// - /// Delegate called by ArpTableUpdated - /// - /// contains the entire ARP table and a bool to note if there was an error in retrieving the data - public delegate void ArpTableEventHandler(ArpTableEventArgs args); - - private static readonly char NewLineSplitter = CrestronEnvironment.NewLine.ToCharArray().First(); - private static readonly string NewLine = CrestronEnvironment.NewLine; - - private static readonly object _lock = new(); - - /// - /// Last resolved ARP table - it is recommended to refresh the arp before using this. - /// - public static List ArpTable { get; private set; } - - /// - /// Force recheck of ARP table - /// - public static void RefreshArp() + public static class NetworkDeviceHelpers { - var error = false; - lock (_lock) + /// + /// Event raised when ArpTable changes + /// + public static event ArpTableEventHandler ArpTableUpdated; + + /// + /// Delegate called by ArpTableUpdated + /// + /// contains the entire ARP table and a bool to note if there was an error in retrieving the data + public delegate void ArpTableEventHandler(ArpTableEventArgs args); + + private static readonly char NewLineSplitter = CrestronEnvironment.NewLine.ToCharArray().First(); + private static readonly string NewLine = CrestronEnvironment.NewLine; + + private static readonly CCriticalSection Lock = new CCriticalSection(); + + /// + /// Gets or sets the ArpTable + /// + public static List ArpTable { get; private set; } + + /// + /// RefreshArp method + /// + public static void RefreshArp() { + var error = false; try { + Lock.Enter(); var consoleResponse = string.Empty; if (!CrestronConsole.SendControlSystemCommand("showarptable", ref consoleResponse)) return; if (string.IsNullOrEmpty(consoleResponse)) @@ -75,152 +75,164 @@ public static class NetworkDeviceHelpers Debug.LogMessage(LogEventLevel.Information, "Exception in \"RefreshArp\" : {0}", ex.Message); error = true; } - } // end lock - - OnArpTableUpdated(new ArpTableEventArgs(ArpTable, error)); - } + finally + { + Lock.Leave(); + OnArpTableUpdated(new ArpTableEventArgs(ArpTable, error)); + } + } - private static void OnArpTableUpdated(ArpTableEventArgs args) - { - if (args == null) return; - var handler = ArpTableUpdated; - if (handler == null) return; - handler.Invoke(args); - } + private static void OnArpTableUpdated(ArpTableEventArgs args) + { + if (args == null) return; + var handler = ArpTableUpdated; + if (handler == null) return; + handler.Invoke(args); + } + + static NetworkDeviceHelpers() + { + ArpTable = new List(); + } + + /// + /// Removes leading zeros, leading whitespace, and trailing whitespace from an IPAddress string + /// + /// Ip Address to Santitize + /// Sanitized Ip Address + /// + /// SanitizeIpAddress method + /// + public static string SanitizeIpAddress(string ipAddressIn) + { + try + { + var ipAddress = IPAddress.Parse(ipAddressIn.TrimStart('0')); + return ipAddress.ToString(); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to Santize Ip : {0}", ex.Message); + return ipAddressIn; + } + } + + /// + /// Resolves a hostname by IP Address using DNS + /// + /// IP Address to resolve from + /// Resolved Hostname - on failure to determine hostname, will return IP Address + /// + /// ResolveHostnameFromIp method + /// + public static string ResolveHostnameFromIp(string ipAddress) + { + try + { + var santitizedIp = SanitizeIpAddress(ipAddress); + var hostEntry = Dns.GetHostEntry(santitizedIp); + return hostEntry == null ? ipAddress : hostEntry.HostName; + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Information, "Exception Resolving Hostname from IP Address : {0}", ex.Message); + return ipAddress; + } + } + + /// + /// Resolves an IP Address by hostname using DNS + /// + /// Hostname to resolve from + /// Resolved IP Address - on a failure to determine IP Address, will return hostname + /// + /// ResolveIpFromHostname method + /// + public static string ResolveIpFromHostname(string hostName) + { + try + { + var hostEntry = Dns.GetHostEntry(hostName); + return hostEntry == null ? hostName : hostEntry.AddressList.First().ToString(); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Information, "Exception Resolving IP Address from Hostname : {0}", ex.Message); + return hostName; + } + } - static NetworkDeviceHelpers() - { - ArpTable = new List(); } /// - /// Removes leading zeros, leading whitespace, and trailing whitespace from an IPAddress string + /// Represents a ArpEntry /// - /// Ip Address to Santitize - /// Sanitized Ip Address - public static string SanitizeIpAddress(string ipAddressIn) + public class ArpEntry { - try + /// + /// The IP Address of the ARP Entry + /// + public readonly IPAddress IpAddress; + + /// + /// The MAC Address of the ARP Entry + /// + public readonly string MacAddress; + + /// + /// Constructs new ArpEntry object + /// + /// string formatted as ipv4 address + /// mac address string - format is unimportant + public ArpEntry(string ipAddress, string macAddress) { - var ipAddress = IPAddress.Parse(ipAddressIn.TrimStart('0')); - return ipAddress.ToString(); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to Santize Ip : {0}", ex.Message); - return ipAddressIn; + if (string.IsNullOrEmpty(ipAddress)) + { + throw new ArgumentException("\"ipAddress\" cannot be null or empty"); + } + if (string.IsNullOrEmpty(macAddress)) + { + throw new ArgumentException("\"macAddress\" cannot be null or empty"); + } + IpAddress = IPAddress.Parse(ipAddress.TrimStart().TrimStart('0').TrimEnd()); + MacAddress = macAddress; } } /// - /// Resolves a hostname by IP Address using DNS + /// Represents a ArpTableEventArgs /// - /// IP Address to resolve from - /// Resolved Hostname - on failure to determine hostname, will return IP Address - public static string ResolveHostnameFromIp(string ipAddress) + public class ArpTableEventArgs : EventArgs { - try + /// + /// The retrieved ARP Table + /// + public readonly List ArpTable; + /// + /// True if there was a problem retrieving the ARP Table + /// + public readonly bool Error; + + /// + /// Constructor for ArpTableEventArgs + /// + /// The entirety of the retrieved ARP table + /// True of an error was encountered updating the ARP table + public ArpTableEventArgs(List arpTable, bool error) { - var santitizedIp = SanitizeIpAddress(ipAddress); - var hostEntry = Dns.GetHostEntry(santitizedIp); - return hostEntry == null ? ipAddress : hostEntry.HostName; + ArpTable = arpTable; + Error = error; } - catch (Exception ex) + + /// + /// Constructor for ArpTableEventArgs - assumes no error encountered in retrieving ARP Table + /// + /// The entirety of the retrieved ARP table + public ArpTableEventArgs(List arpTable) { - Debug.LogMessage(LogEventLevel.Information, "Exception Resolving Hostname from IP Address : {0}", ex.Message); - return ipAddress; + ArpTable = arpTable; + Error = false; } } - - /// - /// Resolves an IP Address by hostname using DNS - /// - /// Hostname to resolve from - /// Resolved IP Address - on a failure to determine IP Address, will return hostname - public static string ResolveIpFromHostname(string hostName) - { - try - { - var hostEntry = Dns.GetHostEntry(hostName); - return hostEntry == null ? hostName : hostEntry.AddressList.First().ToString(); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Information, "Exception Resolving IP Address from Hostname : {0}", ex.Message); - return hostName; - } - } - -} - -/// -/// Object to hold data about an arp entry -/// -public class ArpEntry -{ - /// - /// IP Address of the ARP entry - /// - public readonly IPAddress IpAddress; - - /// - /// MAC Address of the ARP entry - /// - public readonly string MacAddress; - - /// - /// Constructs new ArpEntry object - /// - /// string formatted as ipv4 address - /// mac address string - format is unimportant - public ArpEntry(string ipAddress, string macAddress) - { - if (string.IsNullOrEmpty(ipAddress)) - { - throw new ArgumentException("\"ipAddress\" cannot be null or empty"); - } - if (string.IsNullOrEmpty(macAddress)) - { - throw new ArgumentException("\"macAddress\" cannot be null or empty"); - } - IpAddress = IPAddress.Parse(ipAddress.TrimStart().TrimStart('0').TrimEnd()); - MacAddress = macAddress; - } -} - -/// -/// Arguments passed by the ArpTableUpdated event -/// -public class ArpTableEventArgs : EventArgs -{ - /// - /// The retrieved ARP Table - /// - public readonly List ArpTable; - /// - /// True if there was a problem retrieving the ARP Table - /// - public readonly bool Error; - - /// - /// Constructor for ArpTableEventArgs - /// - /// The entirety of the retrieved ARP table - /// True of an error was encountered updating the ARP table - public ArpTableEventArgs(List arpTable, bool error) - { - ArpTable = arpTable; - Error = error; - } - - /// - /// Constructor for ArpTableEventArgs - assumes no error encountered in retrieving ARP Table - /// - /// The entirety of the retrieved ARP table - public ArpTableEventArgs(List arpTable) - { - ArpTable = arpTable; - Error = false; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAnnotationStream.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAnnotationStream.cs deleted file mode 100644 index 7bef3671..00000000 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAnnotationStream.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - /// - /// Defines minimum functionality for a device that can provide an annotation stream. - /// - public interface IAnnotationStream - { - /// - /// Starts the annotation stream. - /// - /// The index of the annotation stream to start. - bool StartAnnotationForStream(ushort streamIdx); - - /// - /// Stops the annotation stream. - /// - /// The index of the annotation stream to stop. - bool StopAnnotationForStream(ushort streamIdx); - } diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAudioZones.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAudioZones.cs index cf7fa623..540b7a03 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAudioZones.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IAudioZones.cs @@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Core /// /// Identifies a device that contains audio zones /// - public interface IAudioZones : IRoutingMidpointWithFeedback + public interface IAudioZones : IRouting { /// /// Gets the collection of audio zones diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IBasicVolumeWithFeedbackAdvanced.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IBasicVolumeWithFeedbackAdvanced.cs index 5baece21..f0a018c5 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IBasicVolumeWithFeedbackAdvanced.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IBasicVolumeWithFeedbackAdvanced.cs @@ -14,10 +14,5 @@ namespace PepperDash.Essentials.Core /// Gets the volume level units /// eVolumeLevelUnits Units { get; } - - /// - /// Gets the maximum volume level - /// - int MaxVolumeLevel { get; } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IChannel.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IChannel.cs index 3dda43e4..c8c54006 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IChannel.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IChannel.cs @@ -4,8 +4,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines the contract for IChannel /// @@ -79,4 +79,5 @@ namespace PepperDash.Essentials.Core; triList.ClearBoolSigAction(129); triList.ClearBoolSigAction(134); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IColorFunctions.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IColorFunctions.cs index 51f8e44a..69acc8f6 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IColorFunctions.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IColorFunctions.cs @@ -4,8 +4,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// IColor interface /// @@ -66,4 +66,5 @@ namespace PepperDash.Essentials.Core; triList.ClearBoolSigAction(157); triList.ClearBoolSigAction(158); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDPad.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDPad.cs index 9e26cd5a..ec9362b8 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDPad.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDPad.cs @@ -4,8 +4,8 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -86,4 +86,5 @@ namespace PepperDash.Essentials.Core; triList.ClearBoolSigAction(130); triList.ClearBoolSigAction(134); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDiscPlayerControls.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDiscPlayerControls.cs index b3d944e6..5deee690 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDiscPlayerControls.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDiscPlayerControls.cs @@ -3,11 +3,14 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; +namespace PepperDash.Essentials.Core +{ /// /// Defines the contract for IDiscPlayerControls /// public interface IDiscPlayerControls : IColor, IDPad, INumericKeypad, IHasPowerControl, ITransport, IUiDisplayInfo { - } \ No newline at end of file + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplay.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplay.cs index 2acceb89..af3428f6 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplay.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplay.cs @@ -1,14 +1,15 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Interface for display devices that can be controlled and monitored. -/// This interface combines functionality for feedback, routing, power control, -/// warming/cooling, usage tracking, and key name management. -/// It is designed to be implemented by devices that require these capabilities, -/// such as projectors, displays, and other visual output devices. -/// -public interface IDisplay : IHasFeedback, IRoutingSinkWithFeedback, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { + /// + /// Interface for display devices that can be controlled and monitored. + /// This interface combines functionality for feedback, routing, power control, + /// warming/cooling, usage tracking, and key name management. + /// It is designed to be implemented by devices that require these capabilities, + /// such as projectors, displays, and other visual output devices. + /// + public interface IDisplay : IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName + { + } } diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplayBasic.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplayBasic.cs new file mode 100644 index 00000000..b76d3145 --- /dev/null +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDisplayBasic.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Essentials.Core.Devices.DeviceTypeInterfaces +{ + /// + /// Defines the contract for IDisplayBasic + /// + public interface IDisplayBasic + { + /// + /// Sets the input to HDMI 1 + /// + void InputHdmi1(); + + /// + /// Sets the input to HDMI 2 + /// + void InputHdmi2(); + + /// + /// Sets the input to HDMI 3 + /// + void InputHdmi3(); + + /// + /// Sets the input to HDMI 4 + /// + void InputHdmi4(); + + /// + /// Sets the input to DisplayPort 1 + /// + void InputDisplayPort1(); + + /// + /// Sets the input to DVI 1 + /// + void InputDvi1(); + + /// + /// Sets the input to Video 1 + /// + void InputVideo1(); + + /// + /// Sets the input to VGA 1 + /// + void InputVga1(); + + /// + /// Sets the input to VGA 2 + /// + void InputVga2(); + + /// + /// Sets the input to RGB 1 + /// + void InputRgb1(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDumbSource.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDumbSource.cs index ba7f1a43..2f419498 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDumbSource.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDumbSource.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for IDumbSource -/// -public interface IDumbSource +namespace PepperDash.Essentials.Core { + /// + /// Defines the contract for IDumbSource + /// + public interface IDumbSource + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDvr.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDvr.cs index 4397db50..527e8d46 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDvr.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IDvr.cs @@ -9,8 +9,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -55,4 +55,5 @@ namespace PepperDash.Essentials.Core; triList.ClearBoolSigAction(136); triList.ClearBoolSigAction(152); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IEmergencyOSD.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IEmergencyOSD.cs index 66d74c3e..efc068b0 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IEmergencyOSD.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IEmergencyOSD.cs @@ -1,20 +1,25 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; - -/// -/// Defines the contract for IEmergencyOSD -/// -public interface IEmergencyOSD +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Shows an emergency message on the OSD + /// Defines the contract for IEmergencyOSD /// - /// The URL of the emergency message to display - void ShowEmergencyMessage(string url); + public interface IEmergencyOSD + { + /// + /// Shows an emergency message on the OSD + /// + /// The URL of the emergency message to display + void ShowEmergencyMessage(string url); - /// - /// Hides the emergency message from the OSD - /// - void HideEmergencyMessage(); + /// + /// Hides the emergency message from the OSD + /// + void HideEmergencyMessage(); + } } - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasBranding.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasBranding.cs index 75f04156..c05ef572 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasBranding.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasBranding.cs @@ -1,18 +1,21 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; +using System; -/// -/// Defines the contract for IHasBranding -/// -public interface IHasBranding +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets whether branding is enabled + /// Defines the contract for IHasBranding /// - bool BrandingEnabled { get; } + public interface IHasBranding + { + /// + /// Gets whether branding is enabled + /// + bool BrandingEnabled { get; } - /// - /// Initializes branding for the device - /// - /// The key identifying the room for branding purposes - void InitializeBranding(string roomKey); -} + /// + /// Initializes branding for the device + /// + /// The key identifying the room for branding purposes + void InitializeBranding(string roomKey); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasDynamicMultiviewLayout.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasDynamicMultiviewLayout.cs deleted file mode 100644 index 786e2548..00000000 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasDynamicMultiviewLayout.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines a device (e.g. a multiview-capable video decoder) that can build a multiview tile -/// layout at runtime from a set of sources with priority values, rather than only supporting -/// pre-configured/named layouts. Implementing this interface (instead of requiring consumers to -/// reference the hardware-specific plugin type directly) lets other plugins (e.g. a room plugin) -/// drive dynamic, priority-based layouts on the device without taking a compile-time dependency on -/// the specific hardware plugin that implements it. -/// -public interface IHasDynamicMultiviewLayout -{ - /// - /// Computes and applies a multiview layout from the given participant sources (ordered by - /// priority) and an optional active presentation source. - /// - /// Sources to place in participant tiles, each with a priority (lower value = higher priority). - /// Device key for the active presentation source, or null/empty if no presentation is active. - /// True if the layout was successfully applied. - bool ApplyDynamicLayout(IReadOnlyList participantSources, string presentationSourceKey); -} - -/// -/// Represents a single source eligible for placement in a dynamic multiview layout, along with -/// its priority. Lower priority values are placed first / given more prominent tiles (i.e. lower -/// number = higher priority). -/// -public class MultiviewParticipantSource -{ - /// - /// The device key of the source to place in a tile. - /// - [JsonProperty("sourceKey")] - public string SourceKey { get; set; } - - /// - /// The priority of this source. Lower values are placed first / given more prominent tiles. - /// - [JsonProperty("priority")] - public int Priority { get; set; } - - /// - /// Parameterless constructor for deserialization. - /// - public MultiviewParticipantSource() - { - } - - /// - /// Initializes a new instance of the MultiviewParticipantSource class. - /// - public MultiviewParticipantSource(string sourceKey, int priority) - { - SourceKey = sourceKey; - Priority = priority; - } -} diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasFarEndContentStatus.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasFarEndContentStatus.cs index 1595fa6e..5a8f66ee 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasFarEndContentStatus.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasFarEndContentStatus.cs @@ -1,13 +1,13 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for IHasFarEndContentStatus -/// -public interface IHasFarEndContentStatus +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets whether far end content is being received + /// Defines the contract for IHasFarEndContentStatus /// - BoolFeedback ReceivingContent { get; } -} + public interface IHasFarEndContentStatus + { + /// + /// Gets whether far end content is being received + /// + BoolFeedback ReceivingContent { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasInputs.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasInputs.cs index d6fae39d..42e97b4b 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasInputs.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasInputs.cs @@ -1,20 +1,19 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Describes a device that has selectable inputs -/// -/// the type to use as the key for each input item. Most likely an enum or string\ -/// -/// See MockDisplay for example implemntation -/// -public interface IHasInputs : IKeyName +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets the collection of inputs + /// Describes a device that has selectable inputs /// - ISelectableItems Inputs { get; } + /// the type to use as the key for each input item. Most likely an enum or string\ + /// + /// See MockDisplay for example implemntation + /// + public interface IHasInputs : IKeyName + { + /// + /// Gets the collection of inputs + /// + ISelectableItems Inputs { get; } + } } - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasMultiviewControl.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasMultiviewControl.cs deleted file mode 100644 index 58bdd1f3..00000000 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasMultiviewControl.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines a device (e.g. a multiview/UVC compositor) whose multiview output can be enabled and -/// whose active layout can be observed. Implementing this interface (instead of requiring consumers -/// to reference the hardware-specific plugin type directly) lets other plugins (e.g. a room plugin) -/// turn the multiview on and read its current layout without taking a compile-time dependency on the -/// specific hardware plugin that implements it. The layout is set via -/// . -/// -public interface IHasMultiviewControl -{ - /// - /// Enables or disables the multiview output. - /// - /// True to enable multiview, false to disable. - void SetMultiviewEnabled(bool enabled); - - /// - /// Feedback for whether the multiview output is currently enabled. - /// - BoolFeedback MultiviewEnabled { get; } - - /// - /// Feedback for the current multiview layout index. - /// - IntFeedback MultiviewLayout { get; } -} diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasPhoneDialing.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasPhoneDialing.cs index 170b058d..b884092b 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasPhoneDialing.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasPhoneDialing.cs @@ -1,40 +1,43 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; +using System; +using PepperDash.Essentials.Core; - -/// -/// Defines the contract for IHasPhoneDialing -/// -public interface IHasPhoneDialing +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Feedback that indicates whether the phone is off-hook + /// Defines the contract for IHasPhoneDialing /// - BoolFeedback PhoneOffHookFeedback { get; } + public interface IHasPhoneDialing + { + /// + /// Feedback that indicates whether the phone is off-hook + /// + BoolFeedback PhoneOffHookFeedback { get; } - /// - /// Feedback that provides the caller ID name - /// - StringFeedback CallerIdNameFeedback { get; } + /// + /// Feedback that provides the caller ID name + /// + StringFeedback CallerIdNameFeedback { get; } - /// - /// Feedback that provides the caller ID number - /// - StringFeedback CallerIdNumberFeedback { get; } + /// + /// Feedback that provides the caller ID number + /// + StringFeedback CallerIdNumberFeedback { get; } - /// - /// Dials a phone call to the specified number - /// - /// the number to dial - void DialPhoneCall(string number); + /// + /// Dials a phone call to the specified number + /// + /// the number to dial + void DialPhoneCall(string number); - /// - /// Ends the current phone call - /// - void EndPhoneCall(); + /// + /// Ends the current phone call + /// + void EndPhoneCall(); - /// - /// Sends a DTMF digit to the phone - /// - /// the DTMF digit to send - void SendDtmfToPhone(string digit); -} + /// + /// Sends a DTMF digit to the phone + /// + /// the DTMF digit to send + void SendDtmfToPhone(string digit); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasScreensWithLayouts.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasScreensWithLayouts.cs index 2a67e39b..439462c6 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasScreensWithLayouts.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasScreensWithLayouts.cs @@ -5,102 +5,103 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// This defines a device that has screens with layouts -/// Simply decorative -/// -public interface IHasScreensWithLayouts +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// A dictionary of screens, keyed by screen ID, that contains information about each screen and its layouts. + /// This defines a device that has screens with layouts + /// Simply decorative /// - Dictionary Screens { get; } + public interface IHasScreensWithLayouts + { + /// + /// A dictionary of screens, keyed by screen ID, that contains information about each screen and its layouts. + /// + Dictionary Screens { get; } + + /// + /// Applies a specific layout to a screen based on the provided screen ID and layout index. + /// + /// + /// + void ApplyLayout(uint screenId, uint layoutIndex); + } /// - /// Applies a specific layout to a screen based on the provided screen ID and layout index. + /// Represents information about a screen and its layouts. /// - /// - /// - void ApplyLayout(uint screenId, uint layoutIndex); -} - -/// -/// Represents information about a screen and its layouts. -/// -public class ScreenInfo -{ - - /// - /// Indicates whether the screen is enabled or not. - /// - [JsonProperty("enabled")] - public bool Enabled { get; set; } - - /// - /// The name of the screen. - /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// The index of the screen. - /// - [JsonProperty("screenIndex")] - public int ScreenIndex { get; set; } - - /// - /// A dictionary of layout information for the screen, keyed by layout ID. - /// - [JsonProperty("layouts")] - public Dictionary Layouts { get; set; } -} - -/// -/// Represents information about a layout on a screen. -/// -public class LayoutInfo -{ - /// - /// The name of the layout. - /// - [JsonProperty("layoutName")] - public string LayoutName { get; set; } - - /// - /// The index of the layout. - /// - [JsonProperty("layoutIndex")] - public int LayoutIndex { get; set; } - - /// - /// The type of the layout, which can be "single", "double", "triple", or "quad". - /// - [JsonProperty("layoutType")] - public string LayoutType { get; set; } - - /// - /// A dictionary of window configurations for the layout, keyed by window ID. - /// - [JsonProperty("windows")] - public Dictionary Windows { get; set; } -} - -/// -/// Represents the configuration of a window within a layout on a screen. -/// -public class WindowConfig -{ - /// - /// The display label for the window - /// - [JsonProperty("label")] - public string Label { get; set; } - - /// - /// The input for the window - /// - [JsonProperty("input")] - public string Input { get; set; } + public class ScreenInfo + { + + /// + /// Indicates whether the screen is enabled or not. + /// + [JsonProperty("enabled")] + public bool Enabled { get; set; } + + /// + /// The name of the screen. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// The index of the screen. + /// + [JsonProperty("screenIndex")] + public int ScreenIndex { get; set; } + + /// + /// A dictionary of layout information for the screen, keyed by layout ID. + /// + [JsonProperty("layouts")] + public Dictionary Layouts { get; set; } + } + + /// + /// Represents information about a layout on a screen. + /// + public class LayoutInfo + { + /// + /// The name of the layout. + /// + [JsonProperty("layoutName")] + public string LayoutName { get; set; } + + /// + /// The index of the layout. + /// + [JsonProperty("layoutIndex")] + public int LayoutIndex { get; set; } + + /// + /// The type of the layout, which can be "single", "double", "triple", or "quad". + /// + [JsonProperty("layoutType")] + public string LayoutType { get; set; } + + /// + /// A dictionary of window configurations for the layout, keyed by window ID. + /// + [JsonProperty("windows")] + public Dictionary Windows { get; set; } + } + + /// + /// Represents the configuration of a window within a layout on a screen. + /// + public class WindowConfig + { + /// + /// The display label for the window + /// + [JsonProperty("label")] + public string Label { get; set; } + + /// + /// The input for the window + /// + [JsonProperty("input")] + public string Input { get; set; } + } } diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasSurroundSoundModes.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasSurroundSoundModes.cs index 2a50dd0f..45b24f8b 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasSurroundSoundModes.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasSurroundSoundModes.cs @@ -1,4 +1,9 @@ using PepperDash.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWirelessSharing.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWirelessSharing.cs index bf7562fc..6cd3835e 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWirelessSharing.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHasWirelessSharing.cs @@ -1,47 +1,48 @@ using System; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for a wireless presentation endpoint that reports whether a wireless -/// sharing session is currently active. Implemented by platforms such as Crestron AirMedia, -/// Mersive Solstice, Barco ClickShare, Miracast/Teams receivers, etc. Allows consumers (e.g. -/// room plugins) to react to wireless sharing activity without taking a dependency on any -/// concrete device implementation. -/// -/// -/// This refers specifically to wireless screen/device mirroring, as distinct from in-call -/// content sharing on a video conference. -/// -public interface IHasWirelessSharing +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Reports whether a wireless sharing session is currently active (content is being presented). + /// Defines the contract for a wireless presentation endpoint that reports whether a wireless + /// sharing session is currently active. Implemented by platforms such as Crestron AirMedia, + /// Mersive Solstice, Barco ClickShare, Miracast/Teams receivers, etc. Allows consumers (e.g. + /// room plugins) to react to wireless sharing activity without taking a dependency on any + /// concrete device implementation. /// - BoolFeedback IsSharingFeedback { get; } - - /// - /// Raised when wireless sharing starts or stops. The event args carry the new sharing state. - /// - event EventHandler SharingChanged; -} - -/// -/// Event arguments describing a change in wireless sharing state. -/// -public class WirelessSharingEventArgs : EventArgs -{ - /// - /// True if a wireless sharing session is active (content is being presented), false otherwise. - /// - public bool IsSharing { get; private set; } - - /// - /// Creates a new . - /// - /// True if a wireless sharing session is active, false otherwise. - public WirelessSharingEventArgs(bool isSharing) + /// + /// This refers specifically to wireless screen/device mirroring, as distinct from in-call + /// content sharing on a video conference. + /// + public interface IHasWirelessSharing { - IsSharing = isSharing; + /// + /// Reports whether a wireless sharing session is currently active (content is being presented). + /// + BoolFeedback IsSharingFeedback { get; } + + /// + /// Raised when wireless sharing starts or stops. The event args carry the new sharing state. + /// + event EventHandler SharingChanged; + } + + /// + /// Event arguments describing a change in wireless sharing state. + /// + public class WirelessSharingEventArgs : EventArgs + { + /// + /// True if a wireless sharing session is active (content is being presented), false otherwise. + /// + public bool IsSharing { get; private set; } + + /// + /// Creates a new . + /// + /// True if a wireless sharing session is active, false otherwise. + public WirelessSharingEventArgs(bool isSharing) + { + IsSharing = isSharing; + } } } diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHumiditySensor.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHumiditySensor.cs index f28059c3..748fb860 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHumiditySensor.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IHumiditySensor.cs @@ -1,4 +1,9 @@ - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs deleted file mode 100644 index 55ce51ab..00000000 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IInitializationExceptions.cs +++ /dev/null @@ -1,16 +0,0 @@ - - -using System.Collections.Generic; - -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for devices that can report initialization exceptions. -/// -public interface IInitializationExceptions -{ - /// - /// Gets a list of exceptions that occurred during the initialization of the program. - /// - List InitializationExceptions { get; } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageDefinition.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageDefinition.cs index 4cdffc83..f02c0e05 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageDefinition.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageDefinition.cs @@ -1,57 +1,56 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for ILanguageDefinition -/// -public interface ILanguageDefinition +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// The locale name for the language definition + /// Defines the contract for ILanguageDefinition /// - string LocaleName { get; set; } + public interface ILanguageDefinition + { + /// + /// The locale name for the language definition + /// + string LocaleName { get; set; } - /// - /// The friendly name for the language definition - /// - string FriendlyName { get; set; } + /// + /// The friendly name for the language definition + /// + string FriendlyName { get; set; } - /// - /// Indicates whether the language definition is enabled - /// - bool Enable { get; set; } + /// + /// Indicates whether the language definition is enabled + /// + bool Enable { get; set; } - /// - /// The UI labels for the language definition - /// - List UiLabels { get; set; } + /// + /// The UI labels for the language definition + /// + List UiLabels { get; set; } - /// - /// The source and destination labels for the language definition - /// - List Sources { get; set; } + /// + /// The source and destination labels for the language definition + /// + List Sources { get; set; } - /// - /// The destination labels for the language definition - /// - List Destinations { get; set; } + /// + /// The destination labels for the language definition + /// + List Destinations { get; set; } - /// - /// The source group names for the language definition - /// - List SourceGroupNames { get; set; } + /// + /// The source group names for the language definition + /// + List SourceGroupNames { get; set; } - /// - /// The destination group names for the language definition - /// - List DestinationGroupNames { get; set; } + /// + /// The destination group names for the language definition + /// + List DestinationGroupNames { get; set; } - /// - /// The room names for the language definition - /// - List RoomNames { get; set; } + /// + /// The room names for the language definition + /// + List RoomNames { get; set; } + } } - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageProvider.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageProvider.cs index ac9544ea..9ac22d2b 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageProvider.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILanguageProvider.cs @@ -1,21 +1,22 @@ using System; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for ILanguageProvider -/// -public interface ILanguageProvider +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { + /// - /// The current language definition + /// Defines the contract for ILanguageProvider /// - ILanguageDefinition CurrentLanguage { get; set; } + public interface ILanguageProvider + { + /// + /// The current language definition + /// + ILanguageDefinition CurrentLanguage { get; set; } + + /// + /// Event raised when the current language changes + /// + event EventHandler CurrentLanguageChanged; + } - /// - /// Event raised when the current language changes - /// - event EventHandler CurrentLanguageChanged; } - - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILevelControls.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILevelControls.cs index d4876e3c..e0a1081f 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILevelControls.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ILevelControls.cs @@ -4,17 +4,16 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for ILevelControls -/// -public interface ILevelControls +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// The level control points + /// Defines the contract for ILevelControls /// - Dictionary LevelControlPoints { get; } + public interface ILevelControls + { + /// + /// The level control points + /// + Dictionary LevelControlPoints { get; } + } } - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs index 074d51de..0b1760fa 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControl.cs @@ -1,88 +1,72 @@ using System; -using System.Collections.Generic; using Newtonsoft.Json.Linq; using PepperDash.Core; -using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for IMobileControl -/// -public interface IMobileControl : IKeyed +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { - /// - /// Gets the Host - /// - string Host { get; } /// - /// Gets the Client App URL + /// Defines the contract for IMobileControl /// - string ClientAppUrl { get; } + public interface IMobileControl : IKeyed + { + /// + /// Gets the Host + /// + string Host { get; } - /// - /// Gets the System UUID - /// - string SystemUuid { get; } + /// + /// Gets the Client App URL + /// + string ClientAppUrl { get; } - /// - /// Gets the ApiOnlineAndAuthorized feedback - /// - BoolFeedback ApiOnlineAndAuthorized { get; } + /// + /// Gets the System UUID + /// + string SystemUuid { get; } - /// - /// Sends the message object to the AppServer - /// - /// Message to send - void SendMessageObject(IMobileControlMessage o); + /// + /// Gets the ApiOnlineAndAuthorized feedback + /// + BoolFeedback ApiOnlineAndAuthorized { get; } - /// - /// Adds an action for a messenger - /// - /// Messenger type. Must implement IMobileControlMessenger - /// messenger to register - /// action to add - void AddAction(T messenger, Action action) where T : IMobileControlMessenger; + /// + /// Sends the message object to the AppServer + /// + /// Message to send + void SendMessageObject(IMobileControlMessage o); - /// - /// Removes an action for a messenger - /// - /// key for action - void RemoveAction(string key); + /// + /// Adds an action for a messenger + /// + /// Messenger type. Must implement IMobileControlMessenger + /// messenger to register + /// action to add + void AddAction(T messenger, Action action) where T : IMobileControlMessenger; - /// - /// Adds a device messenger - /// - /// Messenger to add - void AddDeviceMessenger(IMobileControlMessenger messenger); + /// + /// Removes an action for a messenger + /// + /// key for action + void RemoveAction(string key); - /// - /// Check if a device messenger exists - /// - /// Messenger key to find - bool CheckForDeviceMessenger(string key); + /// + /// Adds a device messenger + /// + /// Messenger to add + void AddDeviceMessenger(IMobileControlMessenger messenger); - /// - /// Get a Room Messenger by key - /// - /// messenger key to find - /// Messenger if found, null otherwise - IMobileControlRoomMessenger GetRoomMessenger(string key); + /// + /// Check if a device messenger exists + /// + /// Messenger key to find + bool CheckForDeviceMessenger(string key); - /// - /// Registers default mobile-control messengers for the given device based on the - /// interfaces it implements. When is null all - /// applicable messengers are added; when a list is supplied only messengers whose - /// registry entry type appears in that list are added. - /// - /// The device to add messengers for. - /// - /// Optional filter. Pass typeof(IBasicVolumeControls), - /// typeof(IHasInputs<string>), etc. to restrict which messengers are - /// created. Pass null (or omit the argument) to add all applicable messengers. - /// - void AddDefaultMessengersForDevice(EssentialsDevice device, IEnumerable interfaces = null); - -} + /// + /// Get a Room Messenger by key + /// + /// messenger key to find + /// Messenger if found, null otherwise + IMobileControlRoomMessenger GetRoomMessenger(string key); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessenger.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessenger.cs index bf34dcfb..178289e4 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessenger.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessenger.cs @@ -27,11 +27,5 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces /// /// void RegisterWithAppServer(IMobileControl appServerController); - - /// - /// Unsubscribe a client from this messenger - /// - /// Client ID to remove from subscription list - void UnsubscribeClient(string clientId); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs index aeb95bc2..8603d5b6 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IMobileControlMessengerWithSubscriptions.cs @@ -4,11 +4,22 @@ using PepperDash.Core; namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Obsolete. Use directly. - /// Subscriptions are now always enabled in MessengerBase. + /// Obsolete: messengers are subscription based by default; use IMobileControlMessenger instead. /// - [Obsolete("Use IMobileControlMessenger directly. Subscriptions are always enabled.")] + [Obsolete("This interface is obsolete and will be removed in a future version. All messengers are now subscription based.")] public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger { + /// + /// Unsubscribe a client from this messenger + /// + /// + void UnsubscribeClient(string clientId); + + /// + /// Register this messenger with the AppServerController + /// + /// parent for this messenger + /// Enable messenger subscriptions + void RegisterWithAppServer(IMobileControl appServerController, bool enableMessengerSubscriptions); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/INetworkSwitchControl.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/INetworkSwitchControl.cs index ba3215fe..6c6e5598 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/INetworkSwitchControl.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/INetworkSwitchControl.cs @@ -1,122 +1,122 @@ using System; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Interface for network switches that support VLAN assignment on individual ports. -/// -public interface INetworkSwitchVlanManager +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Returns the current access VLAN ID configured on the port. - /// Return -1 when the value is unavailable (e.g. the switch has not been polled yet - /// or the implementation does not support VLAN queries). + /// Interface for network switches that support VLAN assignment on individual ports. /// - /// Switch port identifier - /// VLAN ID or -1 when unavailable - int GetPortCurrentVlan(string port); - - /// - /// Changes the access VLAN of a single switch port. - /// The implementation is responsible for entering/exiting privileged/config mode. - /// - /// Switch port identifier (e.g. "1/0/3" for Netgear, "gi1/0/3" for Cisco) - /// Target VLAN ID (1-4093) - void SetPortVlan(string port, uint vlanId); -} - -/// -/// Interface for network switches that support Power over Ethernet (PoE) control on individual ports. -/// -public interface INetworkSwitchPoeManager -{ - /// - /// Enables or disables PoE power delivery on a single switch port. - /// The implementation is responsible for entering/exiting privileged/config mode. - /// - /// Switch port identifier - /// True to enable PoE; false to disable PoE - void SetPortPoeState(string port, bool enabled); -} - -/// -/// Standardized interface for network switch devices that support per-port PoE control -/// and VLAN assignment. -/// -public interface INetworkSwitchPoeVlanManager : INetworkSwitchVlanManager, INetworkSwitchPoeManager -{ - /// - /// Event that is raised when the state of a switch port changes, such as a VLAN change or PoE state change. - /// - event EventHandler PortStateChanged; - -} - -/// -/// Event arguments for port state changes on a network switch, such as VLAN changes or PoE state changes. -/// -public class NetworkSwitchPortEventArgs : EventArgs -{ - /// - /// The identifier of the port that changed state (e.g. "1/0/3" for Netgear, "gi1/0/3" for Cisco). - /// - public string Port { get; private set; } - - /// - /// The type of event that occurred on the port (e.g. VLAN change, PoE enabled/disabled). - /// - public NetworkSwitchPortEventType EventType { get; private set; } - - /// - /// Constructor for NetworkSwitchPortEventArgs - /// - /// The identifier of the port that changed state - /// The type of event that occurred on the port - public NetworkSwitchPortEventArgs(string port, NetworkSwitchPortEventType eventType) + public interface INetworkSwitchVlanManager { - Port = port; - EventType = eventType; + /// + /// Returns the current access VLAN ID configured on the port. + /// Return -1 when the value is unavailable (e.g. the switch has not been polled yet + /// or the implementation does not support VLAN queries). + /// + /// Switch port identifier + /// VLAN ID or -1 when unavailable + int GetPortCurrentVlan(string port); + + /// + /// Changes the access VLAN of a single switch port. + /// The implementation is responsible for entering/exiting privileged/config mode. + /// + /// Switch port identifier (e.g. "1/0/3" for Netgear, "gi1/0/3" for Cisco) + /// Target VLAN ID (1-4093) + void SetPortVlan(string port, uint vlanId); + } + + /// + /// Interface for network switches that support Power over Ethernet (PoE) control on individual ports. + /// + public interface INetworkSwitchPoeManager + { + /// + /// Enables or disables PoE power delivery on a single switch port. + /// The implementation is responsible for entering/exiting privileged/config mode. + /// + /// Switch port identifier + /// True to enable PoE; false to disable PoE + void SetPortPoeState(string port, bool enabled); + } + + /// + /// Standardized interface for network switch devices that support per-port PoE control + /// and VLAN assignment. + /// + public interface INetworkSwitchPoeVlanManager : INetworkSwitchVlanManager, INetworkSwitchPoeManager + { + /// + /// Event that is raised when the state of a switch port changes, such as a VLAN change or PoE state change. + /// + event EventHandler PortStateChanged; + + } + + /// + /// Event arguments for port state changes on a network switch, such as VLAN changes or PoE state changes. + /// + public class NetworkSwitchPortEventArgs : EventArgs + { + /// + /// The identifier of the port that changed state (e.g. "1/0/3" for Netgear, "gi1/0/3" for Cisco). + /// + public string Port { get; private set; } + + /// + /// The type of event that occurred on the port (e.g. VLAN change, PoE enabled/disabled). + /// + public NetworkSwitchPortEventType EventType { get; private set; } + + /// + /// Constructor for NetworkSwitchPortEventArgs + /// + /// The identifier of the port that changed state + /// The type of event that occurred on the port + public NetworkSwitchPortEventArgs(string port, NetworkSwitchPortEventType eventType) + { + Port = port; + EventType = eventType; + } + } + + /// + /// Enumeration of network switch port state change event types (e.g. VLAN changes or PoE state changes). + /// + public enum NetworkSwitchPortEventType + { + /// + /// Indicates that the type of event is unknown or cannot be determined. + /// + Unknown, + + /// + /// Indicates that a VLAN change is in progress on the port, either through a call to SetPortVlan or an external change detected by polling. + /// + VlanChangeInProgress, + + /// + /// Indicates that the access VLAN on a port has changed, either through a successful call to SetPortVlan or an external change detected by polling. + /// + VlanChanged, + + /// + /// Indicates that PoE is being disabled on the port, either through a call to SetPortPoeState or an external change detected by polling. + /// + PoeDisableInProgress, + + /// + /// Indicates that PoE has been disabled on the port, either through a successful call to SetPortPoeState or an external change detected by polling. + /// + PoEDisabled, + + /// + /// Indicates that PoE is being enabled on the port, either through a call to SetPortPoeState or an external change detected by polling. + /// + PoeEnableInProgress, + + /// + /// Indicates that PoE has been enabled on the port, either through a successful call to SetPortPoeState or an external change detected by polling. + /// + PoEEnabled } } - -/// -/// Event arguments for port state changes on a network switch, such as VLAN changes or PoE state changes. -/// -public enum NetworkSwitchPortEventType -{ - /// - /// Indicates that the type of event is unknown or cannot be determined. - /// - Unknown, - - /// - /// Indicates that a VLAN change is in progress on the port, either through a call to SetPortVlan or an external change detected by polling. - /// - VlanChangeInProgress, - - /// - /// Indicates that the access VLAN on a port has changed, either through a successful call to SetPortVlan - /// - VlanChanged, - - /// - /// Indicates that PoE is being disabled on the port, either through a call to SetPortPoeState or an external change detected by polling. - /// - PoeDisableInProgress, - - /// - /// Indicates that the PoE state on a port has changed, either through a successful call to SetPortPoeState - /// - PoEDisabled, - - /// - /// Indicates that PoE is being enabled on the port, either through a call to SetPortPoeState or an external change detected by polling. - /// - PoeEnableInProgress, - - /// - /// Indicates that the PoE state on a port has changed, either through a successful call to SetPortPoeState - /// - PoEEnabled -} - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPasswordPrompt.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPasswordPrompt.cs index a068ba19..2ee85341 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPasswordPrompt.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPasswordPrompt.cs @@ -4,62 +4,63 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Describes the functionality required to prompt a user to enter a password -/// -public interface IPasswordPrompt +namespace PepperDash.Essentials.Core { /// - /// Notifies when a password is required or is entered incorrectly + /// Describes the functionality required to prompt a user to enter a password /// - event EventHandler PasswordRequired; - - /// - /// Submits the password - /// - /// - void SubmitPassword(string password); -} - -/// -/// Event args for password prompt events -/// -public class PasswordPromptEventArgs : EventArgs -{ - /// - /// Indicates if the last submitted password was incorrect - /// - public bool LastAttemptWasIncorrect { get; private set; } - - /// - /// Indicates that the login attempt has failed - /// - public bool LoginAttemptFailed { get; private set; } - - /// - /// Indicates that the process was cancelled and the prompt should be dismissed - /// - public bool LoginAttemptCancelled { get; private set; } - - /// - /// A message to be displayed to the user - /// - public string Message { get; private set; } - - /// - /// Constructor for password prompt event args - /// - /// Indicates if the last submitted password was incorrect - /// Indicates that the login attempt has failed - /// Indicates that the process was cancelled and the prompt should be dismissed - /// A message to be displayed to the user - public PasswordPromptEventArgs(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) + public interface IPasswordPrompt { - LastAttemptWasIncorrect = lastAttemptIncorrect; - LoginAttemptFailed = loginFailed; - LoginAttemptCancelled = loginCancelled; - Message = message; + /// + /// Notifies when a password is required or is entered incorrectly + /// + event EventHandler PasswordRequired; + + /// + /// Submits the password + /// + /// The password to submit + void SubmitPassword(string password); + } + + /// + /// PasswordPromptEventArgs class + /// + public class PasswordPromptEventArgs : EventArgs + { + /// + /// Indicates if the last submitted password was incorrect + /// + public bool LastAttemptWasIncorrect { get; private set; } + + /// + /// Gets or sets the LoginAttemptFailed + /// + public bool LoginAttemptFailed { get; private set; } + + /// + /// Gets or sets the LoginAttemptCancelled + /// + public bool LoginAttemptCancelled { get; private set; } + + /// + /// Gets or sets the Message + /// + public string Message { get; private set; } + + /// + /// Constructor + /// + /// indicates if the last submitted password was incorrect + /// indicates if the login attempt failed + /// indicates if the login attempt was cancelled + /// provides a message related to the password prompt + public PasswordPromptEventArgs(bool lastAttemptIncorrect, bool loginFailed, bool loginCancelled, string message) + { + LastAttemptWasIncorrect = lastAttemptIncorrect; + LoginAttemptFailed = loginFailed; + LoginAttemptCancelled = loginCancelled; + Message = message; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPower.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPower.cs index a94dfe12..b94a64ed 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPower.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IPower.cs @@ -1,77 +1,86 @@ -using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.Fusion; -/// -/// Adds feedback for current power state -/// -public interface IHasPowerControlWithFeedback : IHasPowerControl -{ - /// - /// Feedback indicating whether the device is powered on - /// - BoolFeedback PowerIsOnFeedback { get; } -} +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.SmartObjects; - -/// -/// Defines the ability to power a device on and off -/// -public interface IHasPowerControl -{ - /// - /// Powers the device on - /// - void PowerOn(); - - /// - /// Powers the device off - /// - void PowerOff(); - - /// - /// Toggles the power state of the device - /// - void PowerToggle(); -} - - - -/// -/// IHasPowerControlExtensions class -/// -public static class IHasPowerControlExtensions +namespace PepperDash.Essentials.Core { /// - /// LinkButtons method + /// Adds feedback for current power state /// - public static void LinkButtons(this IHasPowerControl dev, BasicTriList triList) + public interface IHasPowerControlWithFeedback : IHasPowerControl { - triList.SetSigFalseAction(101, dev.PowerOn); - triList.SetSigFalseAction(102, dev.PowerOff); - triList.SetSigFalseAction(103, dev.PowerToggle); - - var fbdev = dev as IHasPowerControlWithFeedback; - if (fbdev != null) - { - fbdev.PowerIsOnFeedback.LinkInputSig(triList.BooleanInput[101]); - } + /// + /// Feedback indicating whether the device is powered on + /// + BoolFeedback PowerIsOnFeedback { get; } } /// - /// UnlinkButtons method + /// Defines the ability to power a device on and off /// - public static void UnlinkButtons(this IHasPowerControl dev, BasicTriList triList) + public interface IHasPowerControl { - triList.ClearBoolSigAction(101); - triList.ClearBoolSigAction(102); - triList.ClearBoolSigAction(103); + /// + /// Powers the device on + /// + void PowerOn(); - var fbdev = dev as IHasPowerControlWithFeedback; - if (fbdev != null) - { - fbdev.PowerIsOnFeedback.UnlinkInputSig(triList.BooleanInput[101]); - } + /// + /// Powers the device off + /// + void PowerOff(); + + /// + /// Toggles the power state of the device + /// + void PowerToggle(); } + + /// + /// IHasPowerControlExtensions class + /// + public static class IHasPowerControlExtensions + { + /// + /// LinkButtons method + /// + public static void LinkButtons(this IHasPowerControl dev, BasicTriList triList) + { + triList.SetSigFalseAction(101, dev.PowerOn); + triList.SetSigFalseAction(102, dev.PowerOff); + triList.SetSigFalseAction(103, dev.PowerToggle); + + var fbdev = dev as IHasPowerControlWithFeedback; + if (fbdev != null) + { + fbdev.PowerIsOnFeedback.LinkInputSig(triList.BooleanInput[101]); + } + } + + /// + /// UnlinkButtons method + /// + public static void UnlinkButtons(this IHasPowerControl dev, BasicTriList triList) + { + triList.ClearBoolSigAction(101); + triList.ClearBoolSigAction(102); + triList.ClearBoolSigAction(103); + + var fbdev = dev as IHasPowerControlWithFeedback; + if (fbdev != null) + { + fbdev.PowerIsOnFeedback.UnlinkInputSig(triList.BooleanInput[101]); + } + } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IProjectorScreenLiftControl.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IProjectorScreenLiftControl.cs index efa3a0c4..56aef09e 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IProjectorScreenLiftControl.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IProjectorScreenLiftControl.cs @@ -3,8 +3,8 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces +{ /// /// Defines the contract for IProjectorScreenLiftControl /// @@ -60,4 +60,5 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; /// Screen type device /// screen - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItem.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItem.cs index 410cf563..ff05188e 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItem.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItem.cs @@ -2,28 +2,28 @@ using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - - -/// -/// Defines the contract for ISelectableItem -/// -public interface ISelectableItem : IKeyName +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { - /// - /// Raised when the item is updated - /// - event EventHandler ItemUpdated; /// - /// Gets or sets whether the item is selected + /// Defines the contract for ISelectableItem /// - [JsonProperty("isSelected")] - bool IsSelected { get; set; } + public interface ISelectableItem : IKeyName + { + /// + /// Raised when the item is updated + /// + event EventHandler ItemUpdated; - /// - /// Selects the item - /// - void Select(); -} + /// + /// Gets or sets whether the item is selected + /// + [JsonProperty("isSelected")] + bool IsSelected { get; set; } + + /// + /// Selects the item + /// + void Select(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItems.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItems.cs index 94a03a51..c955a56f 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItems.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISelectableItems.cs @@ -2,41 +2,42 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for ISelectableItems -/// -public interface ISelectableItems where TValue : ISelectableItem +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Raised when the items are updated + /// Defines the contract for ISelectableItems /// - event EventHandler ItemsUpdated; + public interface ISelectableItems where TValue : ISelectableItem + { + /// + /// Raised when the items are updated + /// + event EventHandler ItemsUpdated; + + /// + /// Raised when the current item changes + /// + event EventHandler CurrentItemChanged; + + /// + /// Gets or sets the collection of selectable items + /// + [JsonProperty("items")] + Dictionary Items { get; set; } + + /// + /// Gets or sets the current selected item key + /// + [JsonProperty("currentItem")] + TKey CurrentItem { get; set; } + + } /// - /// Raised when the current item changes + /// Describes a collection of items that can be selected /// - event EventHandler CurrentItemChanged; - - /// - /// Gets or sets the collection of selectable items - /// - [JsonProperty("items")] - Dictionary Items { get; set; } - - /// - /// Gets or sets the current selected item key - /// - [JsonProperty("currentItem")] - TKey CurrentItem { get; set; } -} - -/// -/// Describes a collection of items that can be selected -/// -/// type for the keys in the collection. Probably a string or enum -public interface ISelectableItems : ISelectableItems -{ + /// type for the keys in the collection. Probably a string or enum + public interface ISelectableItems : ISelectableItems + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISetTopBoxControls.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISetTopBoxControls.cs index e195f16f..d8bf969e 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISetTopBoxControls.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ISetTopBoxControls.cs @@ -3,8 +3,8 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines the contract for ISetTopBoxControls /// @@ -81,4 +81,5 @@ namespace PepperDash.Essentials.Core; triList.ClearBoolSigAction(136); triList.ClearBoolSigAction(152); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITemperatureSensor.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITemperatureSensor.cs index e6b09f8b..897855b4 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITemperatureSensor.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITemperatureSensor.cs @@ -1,26 +1,31 @@ -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; - -/// -/// Defines the contract for ITemperatureSensor -/// -public interface ITemperatureSensor +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// The values will range from -400 to +1760 (for -40° to +176° F) or -400 to +800 - /// (for -40° to +80° C)in tenths of a degree. + /// Defines the contract for ITemperatureSensor /// - IntFeedback TemperatureFeedback { get; } + public interface ITemperatureSensor + { + /// + /// The values will range from -400 to +1760 (for -40° to +176° F) or -400 to +800 + /// (for -40° to +80° C)in tenths of a degree. + /// + IntFeedback TemperatureFeedback { get; } - /// - /// The temperature in Celsius format - /// - BoolFeedback TemperatureInCFeedback { get; } + /// + /// The temperature in Celsius format + /// + BoolFeedback TemperatureInCFeedback { get; } - /// - /// Sets the temperature format to Celsius or Fahrenheit - /// - /// If true, sets the format to Celsius; otherwise, sets it to Fahrenheit. - void SetTemperatureFormat(bool setToC); + /// + /// Sets the temperature format to Celsius or Fahrenheit + /// + /// If true, sets the format to Celsius; otherwise, sets it to Fahrenheit. + void SetTemperatureFormat(bool setToC); + } } - diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITransport.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITransport.cs index 4ff2d6ab..917a4b19 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITransport.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITransport.cs @@ -1,97 +1,98 @@ using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for ITransport -/// -public interface ITransport +namespace PepperDash.Essentials.Core { - /// - /// Play button action - /// - /// determines if the button action is a press or release - void Play(bool pressRelease); - - /// - /// Pause button action - /// - /// determines if the button action is a press or release - void Pause(bool pressRelease); - - /// - /// Rewind button action - /// - /// determines if the button action is a press or release - void Rewind(bool pressRelease); - - /// - /// Fast Forward button action - /// - /// determines if the button action is a press or release - void FFwd(bool pressRelease); - - /// - /// Chapter Minus button action - /// - /// determines if the button action is a press or release - void ChapMinus(bool pressRelease); - - /// - /// Chapter Plus button action - /// - /// determines if the button action is a press or release - void ChapPlus(bool pressRelease); - - /// - /// Stop button action - /// - /// determines if the button action is a press or release - void Stop(bool pressRelease); - - /// - /// Record button action - /// - /// determines if the button action is a press or release - void Record(bool pressRelease); -} - -/// -/// ITransportExtensions class -/// -public static class ITransportExtensions -{ - /// - /// Attaches to trilist joins: Play:145, Pause:146, Stop:147, ChapPlus:148, ChapMinus:149, Rewind:150, Ffwd:151, Record:154 - /// - /// The ITransport device - /// The BasicTriList to link buttons to - public static void LinkButtons(this ITransport dev, BasicTriList triList) + /// + /// Defines the contract for ITransport + /// + public interface ITransport { - triList.SetBoolSigAction(145, dev.Play); - triList.SetBoolSigAction(146, dev.Pause); - triList.SetBoolSigAction(147, dev.Stop); - triList.SetBoolSigAction(148, dev.ChapPlus); - triList.SetBoolSigAction(149, dev.ChapMinus); - triList.SetBoolSigAction(150, dev.Rewind); - triList.SetBoolSigAction(151, dev.FFwd); - triList.SetBoolSigAction(154, dev.Record); + /// + /// Play button action + /// + /// determines if the button action is a press or release + void Play(bool pressRelease); + + /// + /// Pause button action + /// + /// determines if the button action is a press or release + void Pause(bool pressRelease); + + /// + /// Rewind button action + /// + /// determines if the button action is a press or release + void Rewind(bool pressRelease); + + /// + /// Fast Forward button action + /// + /// determines if the button action is a press or release + void FFwd(bool pressRelease); + + /// + /// Chapter Minus button action + /// + /// determines if the button action is a press or release + void ChapMinus(bool pressRelease); + + /// + /// Chapter Plus button action + /// + /// determines if the button action is a press or release + void ChapPlus(bool pressRelease); + + /// + /// Stop button action + /// + /// determines if the button action is a press or release + void Stop(bool pressRelease); + + /// + /// Record button action + /// + /// determines if the button action is a press or release + void Record(bool pressRelease); } /// - /// UnlinkButtons method + /// ITransportExtensions class /// - /// The ITransport device - /// The BasicTriList to unlink buttons from - public static void UnlinkButtons(this ITransport dev, BasicTriList triList) + public static class ITransportExtensions { - triList.ClearBoolSigAction(145); - triList.ClearBoolSigAction(146); - triList.ClearBoolSigAction(147); - triList.ClearBoolSigAction(148); - triList.ClearBoolSigAction(149); - triList.ClearBoolSigAction(150); - triList.ClearBoolSigAction(151); - triList.ClearBoolSigAction(154); + /// + /// Attaches to trilist joins: Play:145, Pause:146, Stop:147, ChapPlus:148, ChapMinus:149, Rewind:150, Ffwd:151, Record:154 + /// + /// The ITransport device + /// The BasicTriList to link buttons to + public static void LinkButtons(this ITransport dev, BasicTriList triList) + { + triList.SetBoolSigAction(145, dev.Play); + triList.SetBoolSigAction(146, dev.Pause); + triList.SetBoolSigAction(147, dev.Stop); + triList.SetBoolSigAction(148, dev.ChapPlus); + triList.SetBoolSigAction(149, dev.ChapMinus); + triList.SetBoolSigAction(150, dev.Rewind); + triList.SetBoolSigAction(151, dev.FFwd); + triList.SetBoolSigAction(154, dev.Record); + } + + /// + /// UnlinkButtons method + /// + /// The ITransport device + /// The BasicTriList to unlink buttons from + public static void UnlinkButtons(this ITransport dev, BasicTriList triList) + { + triList.ClearBoolSigAction(145); + triList.ClearBoolSigAction(146); + triList.ClearBoolSigAction(147); + triList.ClearBoolSigAction(148); + triList.ClearBoolSigAction(149); + triList.ClearBoolSigAction(150); + triList.ClearBoolSigAction(151); + triList.ClearBoolSigAction(154); + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITvPresetsProvider.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITvPresetsProvider.cs index 0d6fce71..09b7066c 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITvPresetsProvider.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/ITvPresetsProvider.cs @@ -1,14 +1,15 @@ using PepperDash.Essentials.Core.Presets; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for ITvPresetsProvider -/// -public interface ITvPresetsProvider +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// The TV presets model + /// Defines the contract for ITvPresetsProvider /// - DevicePresetsModel TvPresets { get; } -} + public interface ITvPresetsProvider + { + /// + /// The TV presets model + /// + DevicePresetsModel TvPresets { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IUiDisplayInfo.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IUiDisplayInfo.cs index 274cb06a..231abf1f 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IUiDisplayInfo.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IUiDisplayInfo.cs @@ -1,7 +1,7 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines the contract for IUiDisplayInfo /// @@ -11,4 +11,5 @@ namespace PepperDash.Essentials.Core; /// Display UI Type /// uint DisplayUiType { get; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IWarmingCooling.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IWarmingCooling.cs index 54ac37f8..e69bf35c 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IWarmingCooling.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/IWarmingCooling.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines a class that has warm up and cool down /// @@ -20,4 +20,5 @@ namespace PepperDash.Essentials.Core; /// Feedback indicating whether the device is cooling down /// BoolFeedback IsCoolingDownFeedback { get; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/LanguageLabel.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/LanguageLabel.cs index 7f83a829..c8a45592 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/LanguageLabel.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/LanguageLabel.cs @@ -1,26 +1,28 @@ - -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; +using System; +using PepperDash.Core; - -/// -/// Represents a LanguageLabel -/// -public class LanguageLabel +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets or sets the Key + /// Represents a LanguageLabel /// - public string Key { get; set; } - /// - /// Gets or sets the Description - /// - public string Description { get; set; } - /// - /// Gets or sets the DisplayText - /// - public string DisplayText { get; set; } - /// - /// Gets or sets the JoinNumber - /// - public uint JoinNumber { get; set; } -} + public class LanguageLabel + { + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + /// + /// Gets or sets the Description + /// + public string Description { get; set; } + /// + /// Gets or sets the DisplayText + /// + public string DisplayText { get; set; } + /// + /// Gets or sets the JoinNumber + /// + public uint JoinNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/Template.cs b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/Template.cs index bf8dedd6..31f71df9 100644 --- a/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/Template.cs +++ b/src/PepperDash.Essentials.Core/DeviceTypeInterfaces/Template.cs @@ -3,6 +3,7 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.SmartObjects; -namespace PepperDash.Essentials.Core; - - \ No newline at end of file +namespace PepperDash.Essentials.Core +{ + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/AudioControlListItemBase.cs b/src/PepperDash.Essentials.Core/Devices/AudioControlListItemBase.cs index ec555c79..038f80a7 100644 --- a/src/PepperDash.Essentials.Core/Devices/AudioControlListItemBase.cs +++ b/src/PepperDash.Essentials.Core/Devices/AudioControlListItemBase.cs @@ -5,40 +5,41 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core; - -/// -/// Base class for items that can be added to an AudioControlList. Contains properties that are common to all items, such as the parent device key and an optional item key. Also includes properties for display purposes, such as a name and order. -/// -public abstract class AudioControlListItemBase +namespace PepperDash.Essentials.Core { /// - /// Key of the parent device in the DeviceManager + /// Base class for audio control list items /// - [JsonProperty("parentDeviceKey")] - public string ParentDeviceKey { get; set; } + public abstract class AudioControlListItemBase + { + /// + /// Key of the parent device in the DeviceManager + /// + [JsonProperty("parentDeviceKey")] + public string ParentDeviceKey { get; set; } - /// - /// Optional key of the item in the parent device - /// - [JsonProperty("itemKey")] - public string ItemKey { get; set; } + /// + /// Optional key of the item in the parent device + /// + [JsonProperty("itemKey")] + public string ItemKey { get; set; } - /// - /// A name that will override the items's name on the UI - /// - [JsonProperty("name")] - public string Name { get; set; } + /// + /// A name that will override the items's name on the UI + /// + [JsonProperty("name")] + public string Name { get; set; } - /// - /// Indicates if the item should be included in the user accessible list - /// - [JsonProperty("includeInUserList")] - public bool IncludeInUserList { get; set; } + /// + /// Indicates if the item should be included in the user accessible list + /// + [JsonProperty("includeInUserList")] + public bool IncludeInUserList { get; set; } - /// - /// Used to specify the order of the items in the source list when displayed - /// - [JsonProperty("order")] - public int Order { get; set; } + /// + /// Used to specify the order of the items in the source list when displayed + /// + [JsonProperty("order")] + public int Order { get; set; } + } } diff --git a/src/PepperDash.Essentials.Core/Devices/AudioInterfaces.cs b/src/PepperDash.Essentials.Core/Devices/AudioInterfaces.cs index a9e0dc92..cc886636 100644 --- a/src/PepperDash.Essentials.Core/Devices/AudioInterfaces.cs +++ b/src/PepperDash.Essentials.Core/Devices/AudioInterfaces.cs @@ -6,8 +6,8 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Enumeration of AudioChangeType values @@ -50,4 +50,5 @@ namespace PepperDash.Essentials.Core; ChangeType = changeType; AudioDevice = device; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs index 91ca3c83..a154c083 100644 --- a/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/CameraListItem.cs @@ -1,82 +1,82 @@ using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Represents an item in a camera list, which can be used to display camera sources in a user interface. Contains properties for the device key, preferred name, icon, and order of the item in the list. Also includes a property to get the associated camera device from the DeviceManager based on the device key. -/// -public class CameraListItem +namespace PepperDash.Essentials.Core { - /// - /// Key of the camera device in the DeviceManager + /// Represents a CameraListItem /// - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - /// - /// Returns the source Device for this, if it exists in DeviceManager - /// - [JsonIgnore] - public Device CameraDevice + public class CameraListItem { - get - { - if (_cameraDevice == null) - _cameraDevice = DeviceManager.GetDeviceForKey(DeviceKey) as Device; - return _cameraDevice; - } - } - Device _cameraDevice; + /// + /// Key of the camera device + /// + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } - /// - /// Gets either the source's Name or this AlternateName property, if - /// defined. If source doesn't exist, returns "Missing source" - /// - [JsonProperty("preferredName")] - public string PreferredName - { - get + /// + /// Returns the source Device for this, if it exists in DeviceManager + /// + [JsonIgnore] + public Device CameraDevice { - if (string.IsNullOrEmpty(Name)) + get { - if (CameraDevice == null) - return "---"; - return CameraDevice.Name; + if (_cameraDevice == null) + _cameraDevice = DeviceManager.GetDeviceForKey(DeviceKey) as Device; + return _cameraDevice; } - return Name; } - } + Device _cameraDevice; - /// - /// A name that will override the source's name on the UI - /// - [JsonProperty("name")] - public string Name { get; set; } + /// + /// Gets either the source's Name or this AlternateName property, if + /// defined. If source doesn't exist, returns "Missing source" + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (string.IsNullOrEmpty(Name)) + { + if (CameraDevice == null) + return "---"; + return CameraDevice.Name; + } + return Name; + } + } + + /// + /// A name that will override the source's name on the UI + /// + [JsonProperty("name")] + public string Name { get; set; } - /// - /// Specifies and icon for the source list item - /// - [JsonProperty("icon")] - public string Icon { get; set; } + /// + /// Specifies and icon for the source list item + /// + [JsonProperty("icon")] + public string Icon { get; set; } - /// - /// Alternate icon - /// + /// + /// Alternate icon + /// [JsonProperty("altIcon", NullValueHandling = NullValueHandling.Ignore)] - public string AltIcon { get; set; } + public string AltIcon { get; set; } - /// - /// Indicates if the item should be included in the user facing list - /// + /// + /// Indicates if the item should be included in the user facing list + /// [JsonProperty("includeInUserList")] - public bool IncludeInUserList { get; set; } + public bool IncludeInUserList { get; set; } - /// - /// Used to specify the order of the items in the source list when displayed - /// + /// + /// Used to specify the order of the items in the source list when displayed + /// [JsonProperty("order")] - public int Order { get; set; } + public int Order { get; set; } + } } diff --git a/src/PepperDash.Essentials.Core/Devices/CodecInterfaces.cs b/src/PepperDash.Essentials.Core/Devices/CodecInterfaces.cs index d757cd5a..b9d8a28f 100644 --- a/src/PepperDash.Essentials.Core/Devices/CodecInterfaces.cs +++ b/src/PepperDash.Essentials.Core/Devices/CodecInterfaces.cs @@ -4,106 +4,107 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Adds control of codec receive volume -/// -public interface IReceiveVolume -{ - // Break this out into 3 interfaces - - /// - /// Sets the receive volume level - /// - /// volume level to set - void SetReceiveVolume(ushort level); - - /// - /// Mutes the receive audio - /// - void ReceiveMuteOn(); - - /// - /// Unmutes the receive audio - /// - void ReceiveMuteOff(); - - /// - /// Toggles the receive mute state - /// - void ReceiveMuteToggle(); - - /// - /// Feedback for the receive volume level - /// - IntFeedback ReceiveLevelFeedback { get; } - - /// - /// Feedback for the receive mute state - /// - BoolFeedback ReceiveMuteIsOnFeedback { get; } -} - -/// -/// Defines the contract for ITransmitVolume -/// -public interface ITransmitVolume +namespace PepperDash.Essentials.Core { /// - /// Sets the transmit volume level + /// Adds control of codec receive volume /// - /// volume level to set - void SetTransmitVolume(ushort level); + public interface IReceiveVolume + { + // Break this out into 3 interfaces + + /// + /// Sets the receive volume level + /// + /// volume level to set + void SetReceiveVolume(ushort level); + + /// + /// Mutes the receive audio + /// + void ReceiveMuteOn(); + + /// + /// Unmutes the receive audio + /// + void ReceiveMuteOff(); + + /// + /// Toggles the receive mute state + /// + void ReceiveMuteToggle(); + + /// + /// Feedback for the receive volume level + /// + IntFeedback ReceiveLevelFeedback { get; } + + /// + /// Feedback for the receive mute state + /// + BoolFeedback ReceiveMuteIsOnFeedback { get; } + } /// - /// Mutes the transmit audio + /// Defines the contract for ITransmitVolume /// - void TransmitMuteOn(); + public interface ITransmitVolume + { + /// + /// Sets the transmit volume level + /// + /// volume level to set + void SetTransmitVolume(ushort level); + + /// + /// Mutes the transmit audio + /// + void TransmitMuteOn(); + + /// + /// Unmutes the transmit audio + /// + void TransmitMuteOff(); + + /// + /// Toggles the transmit mute state + /// + void TransmitMuteToggle(); + + /// + /// Feedback for the transmit volume level + /// + IntFeedback TransmitLevelFeedback { get; } + + /// + /// Feedback for the transmit mute state + /// + BoolFeedback TransmitMuteIsOnFeedback { get; } + } /// - /// Unmutes the transmit audio + /// Defines the contract for IPrivacy /// - void TransmitMuteOff(); + public interface IPrivacy + { + /// + /// Enables privacy mode + /// + void PrivacyModeOn(); - /// - /// Toggles the transmit mute state - /// - void TransmitMuteToggle(); + /// + /// Disables privacy mode + /// + void PrivacyModeOff(); - /// - /// Feedback for the transmit volume level - /// - IntFeedback TransmitLevelFeedback { get; } + /// + /// Toggles privacy mode + /// + void PrivacyModeToggle(); - /// - /// Feedback for the transmit mute state - /// - BoolFeedback TransmitMuteIsOnFeedback { get; } -} - -/// -/// Defines the contract for IPrivacy -/// -public interface IPrivacy -{ - /// - /// Enables privacy mode - /// - void PrivacyModeOn(); - - /// - /// Disables privacy mode - /// - void PrivacyModeOff(); - - /// - /// Toggles privacy mode - /// - void PrivacyModeToggle(); - - /// - /// Feedback for the privacy mode state - /// - BoolFeedback PrivacyModeIsOnFeedback { get; } -} + /// + /// Feedback for the privacy mode state + /// + BoolFeedback PrivacyModeIsOnFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs new file mode 100644 index 00000000..a1fdef35 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ConfigSnippetAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ConfigSnippetAttribute + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class ConfigSnippetAttribute : Attribute + { + /// + /// Represents a configuration snippet for the device. + /// + /// + public ConfigSnippetAttribute(string configSnippet) + { + ConfigSnippet = configSnippet; + } + + /// + /// Gets the configuration snippet for the device. + /// This snippet can be used in the DeviceConfig to instantiate the device. + /// + public string ConfigSnippet { get; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs b/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs index 29481b34..8c31d4c6 100644 --- a/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs +++ b/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs @@ -8,45 +8,56 @@ using PepperDash.Core; using PepperDash.Essentials.Core.CrestronIO; using Serilog.Events; -namespace PepperDash.Essentials.Core.Devices; - -/// -/// This wrapper class is meant to allow interfaces to be applied to any Crestron processor -/// -public class CrestronProcessor : Device, ISwitchedOutputCollection +namespace PepperDash.Essentials.Core.Devices { - public Dictionary SwitchedOutputs { get; private set; } - - public Crestron.SimplSharpPro.CrestronControlSystem Processor { get; private set; } - - public CrestronProcessor(string key) - : base(key) - { - SwitchedOutputs = new Dictionary(); - Processor = Global.ControlSystem; - - GetRelays(); - } - /// - /// Creates a GenericRelayDevice for each relay on the processor and adds them to the SwitchedOutputs collection + /// This wrapper class is meant to allow interfaces to be applied to any Crestron processor /// - void GetRelays() + public class CrestronProcessor : Device, ISwitchedOutputCollection { - try + /// + /// Collection of switched outputs (relays) on the processor + /// + public Dictionary SwitchedOutputs { get; private set; } + + /// + /// The underlying CrestronControlSystem processor + /// + public Crestron.SimplSharpPro.CrestronControlSystem Processor { get; private set; } + + /// + /// Constructor + /// + /// key for the processor + public CrestronProcessor(string key) + : base(key) { - if (Processor.SupportsRelay) + SwitchedOutputs = new Dictionary(); + Processor = Global.ControlSystem; + + GetRelays(); + } + + /// + /// Creates a GenericRelayDevice for each relay on the processor and adds them to the SwitchedOutputs collection + /// + void GetRelays() + { + try { - for (uint i = 1; i <= Processor.NumberOfRelayPorts; i++) + if (Processor.SupportsRelay) { - var relay = new GenericRelayDevice(string.Format("{0}-relay-{1}", this.Key, i), Processor.RelayPorts[i]); - SwitchedOutputs.Add(i, relay); + for (uint i = 1; i <= Processor.NumberOfRelayPorts; i++) + { + var relay = new GenericRelayDevice(string.Format("{0}-relay-{1}", this.Key, i), Processor.RelayPorts[i]); + SwitchedOutputs.Add(i, relay); + } } } - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Error Getting Relays from processor:\n '{0}'", e); + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Error Getting Relays from processor:\n '{0}'", e); + } } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs b/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs new file mode 100644 index 00000000..abe51665 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/DescriptionAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a description attribute for a device. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public class DescriptionAttribute : Attribute + { + /// + /// Represents a description attribute for a device. + /// + /// + public DescriptionAttribute(string description) + { + Description = description; + } + + /// + /// Gets the description for the device. + /// + public string Description { get; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs index 9ec918a9..5a66c0b3 100644 --- a/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/DestinationListItem.cs @@ -2,124 +2,123 @@ using Newtonsoft.Json; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Routing; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a destination item in a routing system that can receive audio/video signals. -/// Contains information about the destination device, its properties, and location settings. -/// -public class DestinationListItem +namespace PepperDash.Essentials.Core { /// - /// Gets or sets the key identifier for the sink device that this destination represents. + /// Represents a destination item in a routing system that can receive audio/video signals. + /// Contains information about the destination device, its properties, and location settings. /// - [JsonProperty("sinkKey")] - public string SinkKey { get; set; } - - private IRoutingSinkWithFeedback _sinkDevice; - - /// - /// Gets the actual device instance for this destination. - /// Lazily loads the device from the DeviceManager using the SinkKey. - /// - [JsonIgnore] - public IRoutingSinkWithFeedback SinkDevice + public class DestinationListItem { - get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as IRoutingSinkWithFeedback); } - } + /// + /// Gets or sets the key identifier for the sink device that this destination represents. + /// + [JsonProperty("sinkKey")] + public string SinkKey { get; set; } - /// - /// Gets the preferred display name for this destination. - /// Returns the custom Name if set, otherwise returns the SinkDevice name, or "---" if no device is found. - /// - [JsonProperty("preferredName")] - public string PreferredName - { - get + private EssentialsDevice _sinkDevice; + + /// + /// Gets the actual device instance for this destination. + /// Lazily loads the device from the DeviceManager using the SinkKey. + /// + [JsonIgnore] + public EssentialsDevice SinkDevice { - if (!string.IsNullOrEmpty(Name)) - { - return Name; - } - - return SinkDevice == null ? "---" : SinkDevice.Name; + get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as EssentialsDevice); } } + + /// + /// Gets the preferred display name for this destination. + /// Returns the custom Name if set, otherwise returns the SinkDevice name, or "---" if no device is found. + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (!string.IsNullOrEmpty(Name)) + { + return Name; + } + + return SinkDevice == null ? "---" : SinkDevice.Name; + } + } + + /// + /// Gets or sets the custom name for this destination. + /// If set, this name will be used as the PreferredName instead of the device name. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets a value indicating whether this destination should be included in destination lists. + /// + [JsonProperty("includeInDestinationList")] + public bool IncludeInDestinationList { get; set; } + + /// + /// Gets or sets the display order for this destination in lists. + /// Lower values appear first in sorted lists. + /// + [JsonProperty("order")] + public int Order { get; set; } + + /// + /// Gets or sets the surface location identifier for this destination. + /// Used to specify which surface or screen this destination is located on. + /// + [JsonProperty("surfaceLocation")] + public int SurfaceLocation { get; set; } + + /// + /// Gets or sets the vertical location position for this destination. + /// Used for spatial positioning in multi-display configurations. + /// + [JsonProperty("verticalLocation")] + public int VerticalLocation { get; set; } + + /// + /// Gets or sets the horizontal location position for this destination. + /// Used for spatial positioning in multi-display configurations. + /// + [JsonProperty("horizontalLocation")] + public int HorizontalLocation { get; set; } + + /// + /// Gets or sets the signal type that this destination can receive (Audio, Video, AudioVideo, etc.). + /// + [JsonProperty("sinkType")] + public eRoutingSignalType SinkType { get; set; } + + /// + /// Gets or sets a value indicating whether this destination is used for codec content sharing. + /// + [JsonProperty("isCodecContentDestination")] + public bool isCodecContentDestination { get; set; } + + /// + /// Gets or sets a value indicating whether this destination is used for program audio output. + /// + [JsonProperty("isProgramAudioDestination")] + public bool isProgramAudioDestination { get; set; } + + /// + /// Gets or sets a value indicating whether this destination supports USB connections. + /// Indicates if the destination can handle USB functionality, such as USB signal routing or device connections. + /// This property is used to determine compatibility with USB-based devices or systems. + /// + [JsonProperty("supportsUsb")] + public bool SupportsUsb { get; set; } + + /// + /// The key of the destination port associated with this destination item + /// This is used to identify the specific port on the destination device that this item refers to for advanced routing + /// + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } } - - /// - /// Gets or sets the custom name for this destination. - /// If set, this name will be used as the PreferredName instead of the device name. - /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// Gets or sets a value indicating whether this destination should be included in destination lists. - /// - [JsonProperty("includeInDestinationList")] - public bool IncludeInDestinationList { get; set; } - - /// - /// Gets or sets the display order for this destination in lists. - /// Lower values appear first in sorted lists. - /// - [JsonProperty("order")] - public int Order { get; set; } - - /// - /// Gets or sets the surface location identifier for this destination. - /// Used to specify which surface or screen this destination is located on. - /// - [JsonProperty("surfaceLocation")] - public int SurfaceLocation { get; set; } - - /// - /// Gets or sets the vertical location position for this destination. - /// Used for spatial positioning in multi-display configurations. - /// - [JsonProperty("verticalLocation")] - public int VerticalLocation { get; set; } - - /// - /// Gets or sets the horizontal location position for this destination. - /// Used for spatial positioning in multi-display configurations. - /// - [JsonProperty("horizontalLocation")] - public int HorizontalLocation { get; set; } - - /// - /// Gets or sets the signal type that this destination can receive (Audio, Video, AudioVideo, etc.). - /// - [JsonProperty("sinkType")] - public eRoutingSignalType SinkType { get; set; } - - /// - /// Gets or sets a value indicating whether this destination is used for codec content sharing. - /// - [JsonProperty("isCodecContentDestination")] - public bool isCodecContentDestination { get; set; } - - /// - /// Gets or sets a value indicating whether this destination is used for program audio output. - /// - [JsonProperty("isProgramAudioDestination")] - public bool isProgramAudioDestination { get; set; } - - /// - /// Gets or sets a value indicating whether this destination supports USB connections. - /// Indicates if the destination can handle USB functionality, such as USB signal routing or device connections. - /// This property is used to determine compatibility with USB-based devices or systems. - /// - [JsonProperty("supportsUsb")] - public bool SupportsUsb { get; set; } - - /// - /// The key of the destination port associated with this destination item - /// This is used to identify the specific port on the destination device that this item refers to for advanced routing - /// - [JsonProperty("destinationPortKey")] - public string DestinationPortKey { get; set; } - } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceApiBase.cs b/src/PepperDash.Essentials.Core/Devices/DeviceApiBase.cs index 9d15b256..28ac13dc 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceApiBase.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceApiBase.cs @@ -4,20 +4,21 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.Devices; - -/// -/// Base class for all Device APIs -/// -public abstract class DeviceApiBase +namespace PepperDash.Essentials.Core.Devices { /// - /// Action API dictionary + /// Base class for all Device APIs /// - public Dictionary ActionApi { get; protected set; } + public abstract class DeviceApiBase + { + /// + /// Action API dictionary + /// + public Dictionary ActionApi { get; protected set; } - /// - /// Feedback API dictionary - /// - public Dictionary FeedbackApi { get; protected set; } -} + /// + /// Feedback API dictionary + /// + public Dictionary FeedbackApi { get; protected set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceFeedbackExtensions.cs b/src/PepperDash.Essentials.Core/Devices/DeviceFeedbackExtensions.cs index e8bb635a..0e7595e1 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceFeedbackExtensions.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceFeedbackExtensions.cs @@ -6,29 +6,30 @@ using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Extension methods for working with device feedback properties. -/// -public static class DeviceFeedbackExtensions +namespace PepperDash.Essentials.Core { /// - /// Attempts to get and return a feedback property from a device by name. - /// If unsuccessful, returns null. + /// DeviceFeedbackExtensions class /// - /// - /// - /// - public static Feedback GetFeedbackProperty(this Device device, string propertyName) + public static class DeviceFeedbackExtensions { - var feedback = DeviceJsonApi.GetPropertyByName(device.Key, propertyName) as Feedback; - - if (feedback != null) + /// + /// Attempts to get and return a feedback property from a device by name. + /// If unsuccessful, returns null. + /// + /// device to get feedback from + /// name of the feedback property + /// Feedback property if found, otherwise null + public static Feedback GetFeedbackProperty(this Device device, string propertyName) { - return feedback; - } + var feedback = DeviceJsonApi.GetPropertyByName(device.Key, propertyName) as Feedback; - return null; + if (feedback != null) + { + return feedback; + } + + return null; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs index b3e6d48f..c01d5e81 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceJsonApi.cs @@ -7,325 +7,317 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core; - -/// -/// Provides methods for interacting with devices using JSON-formatted commands. -/// -public class DeviceJsonApi +namespace PepperDash.Essentials.Core { /// - /// Executes a method on a device based on a JSON-formatted command string. + /// Represents a DeviceJsonApi /// - /// - public static void DoDeviceActionWithJson(string json) + public class DeviceJsonApi { - if (String.IsNullOrEmpty(json)) + /// + /// DoDeviceActionWithJson method + /// + /// json method + public static void DoDeviceActionWithJson(string json) { - CrestronConsole.ConsoleCommandResponse( - "Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted."); - return; - } - try - { - var action = JsonConvert.DeserializeObject(json); - - DoDeviceAction(action); - } - catch (Exception) - { - CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}"); - } - - } - - - /// - /// Executes a method on a device based on a JSON-formatted command string, awaiting the result if the method is asynchronous. - /// - /// - public static void DoDeviceAction(DeviceActionWrapper action) - { - var key = action.DeviceKey; - var obj = FindObjectOnPath(key); - if (obj == null) - { - CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key); - return; - } - - if (action.Params == null) - { - //no params, so setting action.Params to empty array - action.Params = new object[0]; - } - - Type t = obj.GetType(); - try - { - var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); - - var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); - - if (method == null) + if (String.IsNullOrEmpty(json)) { CrestronConsole.ConsoleCommandResponse( - "Unable to find method with name {0} and that matches parameters {1}", action.MethodName, - action.Params); + "Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted."); return; } - var mParams = method.GetParameters(); - - var convertedParams = BuildConvertedParams(mParams, action.Params); - - Task.Run(() => - { - try - { - Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); - method.Invoke(obj, convertedParams); - } - catch (Exception e) - { - Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); - } - }); - - CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name, - action.DeviceKey); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName, - ex.Message); - } - } - - /// - /// Executes a method on a device based on a JSON-formatted command string, awaiting the result if the method is asynchronous. - /// - /// - /// - public static async Task DoDeviceActionAsync(DeviceActionWrapper action) - { - var key = action.DeviceKey; - var obj = FindObjectOnPath(key); - if (obj == null) - { - Debug.LogMessage(LogEventLevel.Warning, "Unable to find object at path {deviceKey}", null, key); - return; - } - - if (action.Params == null) - { - //no params, so setting action.Params to empty array - action.Params = new object[0]; - } - - Type t = obj.GetType(); - try - { - var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); - - var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); - - if (method == null) - { - Debug.LogMessage(LogEventLevel.Warning, - "Unable to find method with name {methodName} and that matches parameters {@parameters}", null, action.MethodName, - action.Params); - return; - } - var mParams = method.GetParameters(); - - var convertedParams = BuildConvertedParams(mParams, action.Params); - try { - Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params); - var result = method.Invoke(obj, convertedParams); + var action = JsonConvert.DeserializeObject(json); - // If the method returns a Task, await it - if (result is Task task) + DoDeviceAction(action); + } + catch (Exception) + { + CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}"); + } + + } + + + /// + /// DoDeviceAction method + /// + /// action method + public static void DoDeviceAction(DeviceActionWrapper action) + { + var key = action.DeviceKey; + var obj = FindObjectOnPath(key); + if (obj == null) + { + CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key); + return; + } + + if (action.Params == null) + { + //no params, so setting action.Params to empty array + action.Params = new object[0]; + } + + Type t = obj.GetType(); + try + { + var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); + + var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); + + if (method == null) { - await task; + CrestronConsole.ConsoleCommandResponse( + "Unable to find method with name {0} and that matches parameters {1}", action.MethodName, + action.Params); + return; } - // If the method returns a Task, await it - else if (result != null && result.GetType().IsGenericType && result.GetType().GetGenericTypeDefinition() == typeof(Task<>)) + var mParams = method.GetParameters(); + + var convertedParams = mParams + .Select((p, i) => ConvertType(action.Params[i], p.ParameterType)) + .ToArray(); + + Task.Run(() => + { + try + { + Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + method.Invoke(obj, convertedParams); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + } + }); + + CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name, + action.DeviceKey); + } + catch (Exception ex) + { + CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName, + ex.Message); + } + } + + /// + /// DoDeviceActionAsync method + /// + /// action method + public static async Task DoDeviceActionAsync(DeviceActionWrapper action) + { + var key = action.DeviceKey; + var obj = FindObjectOnPath(key); + if (obj == null) + { + Debug.LogMessage(LogEventLevel.Warning, "Unable to find object at path {deviceKey}", null, key); + return; + } + + if (action.Params == null) + { + //no params, so setting action.Params to empty array + action.Params = new object[0]; + } + + Type t = obj.GetType(); + try + { + var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList(); + + var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length); + + if (method == null) { - await (Task)result; + Debug.LogMessage(LogEventLevel.Warning, + "Unable to find method with name {methodName} and that matches parameters {@parameters}", null, action.MethodName, + action.Params); + return; + } + var mParams = method.GetParameters(); + + var convertedParams = mParams + .Select((p, i) => ConvertType(action.Params[i], p.ParameterType)) + .ToArray(); + + try + { + Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params); + var result = method.Invoke(obj, convertedParams); + + // If the method returns a Task, await it + if (result is Task task) + { + await task; + } + // If the method returns a Task, await it + else if (result != null && result.GetType().IsGenericType && result.GetType().GetGenericTypeDefinition() == typeof(Task<>)) + { + await (Task)result; + } + } + catch (Exception e) + { + Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); } } - catch (Exception e) + catch (Exception ex) { - Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey); + Debug.LogMessage(ex, "Unable to call method with name {methodName} with {@parameters}", null, action.MethodName, action.Params); } } - catch (Exception ex) + + private static object ConvertType(object value, Type conversionType) { - Debug.LogMessage(ex, "Unable to call method with name {methodName} with {@parameters}", null, action.MethodName, action.Params); - } - } - - /// - /// Builds the ordered, converted argument array to pass to . - /// - /// - /// Handles the common positional case (one supplied param per method parameter), as well as a method - /// with a single array-typed parameter (e.g. void Foo(string[] items)). For the latter, config - /// authors may either supply the params as a flat list matching the array's contents - /// ("params": ["a", "b", "c"]) or as a single nested array ("params": [["a", "b", "c"]]) - - /// both are converted into the single array argument correctly. - /// - /// The target method's parameters. - /// The raw parameter values supplied in the action. - /// The converted arguments, in order, ready to pass to Invoke. - private static object[] BuildConvertedParams(ParameterInfo[] mParams, object[] actionParams) - { - if (mParams.Length == 1 && mParams[0].ParameterType.IsArray && actionParams.Length != 1) - { - return new[] { ConvertType(actionParams, mParams[0].ParameterType) }; - } - - return mParams - .Select((p, i) => ConvertType(actionParams[i], p.ParameterType)) - .ToArray(); - } - - private static object ConvertType(object value, Type conversionType) - { - if (conversionType.IsArray) - { - var elementType = conversionType.GetElementType(); - - // The supplied value is a collection of raw items (a JArray, or the whole flat params list) - - // convert each item to the array's element type. - if (value is IEnumerable enumerable && !(value is string)) + if (!conversionType.IsEnum) { - var items = enumerable.Cast() - .Select(item => ConvertType(item, elementType)) - .ToArray(); + if (conversionType == typeof(byte[]) && value is string byteString) + { + var unescaped = UnescapeString(byteString); + return System.Text.Encoding.GetEncoding(28591).GetBytes(unescaped); + } - var typedArray = Array.CreateInstance(elementType, items.Length); - Array.Copy(items, typedArray, items.Length); - return typedArray; + var converted = Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); + + if (conversionType == typeof(string) && converted is string s) + { + return UnescapeString(s); + } + + return converted; } - // A single scalar value was supplied for an array parameter - wrap it in a single-element array. - var singleArray = Array.CreateInstance(elementType, 1); - singleArray.SetValue(ConvertType(value, elementType), 0); - return singleArray; + var stringValue = Convert.ToString(value); + + if (String.IsNullOrEmpty(stringValue)) + { + throw new InvalidCastException( + String.Format("{0} cannot be converted to a string prior to conversion to enum")); + } + return Enum.Parse(conversionType, stringValue, true); } - if (!conversionType.IsEnum) + /// + /// Processes escape sequences in a string, converting sequences like \r, \n, \t, \xHH + /// to their corresponding non-printable ASCII characters. + /// + private static string UnescapeString(string input) { - return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture); + if (string.IsNullOrEmpty(input)) + return input; + + return Regex.Replace(input, @"\\(r|n|t|\\|x[0-9A-Fa-f]{2})", match => + { + var seq = match.Groups[1].Value; + switch (seq) + { + case "r": return "\r"; + case "n": return "\n"; + case "t": return "\t"; + case "\\": return "\\"; + default: + // \xHH hex escape + var hex = seq.Substring(1); + return ((char)Convert.ToInt32(hex, 16)).ToString(); + } + }); } - var stringValue = Convert.ToString(value); - - if (String.IsNullOrEmpty(stringValue)) + /// + /// Gets the properties on a device + /// + /// The path to the device object + /// A JSON string representing the properties of the device + public static string GetProperties(string deviceObjectPath) { - throw new InvalidCastException( - String.Format("{0} cannot be converted to a string prior to conversion to enum")); + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; + + Type t = obj.GetType(); + // get the properties and set them into a new collection of NameType wrappers + var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); + return JsonConvert.SerializeObject(props, Formatting.Indented); } - return Enum.Parse(conversionType, stringValue, true); - } - /// - /// Gets the properties on a device - /// - /// The path to the device object. - /// A JSON-formatted string representing the properties of the device. - public static string GetProperties(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; - - Type t = obj.GetType(); - // get the properties and set them into a new collection of NameType wrappers - var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); - return JsonConvert.SerializeObject(props, Formatting.Indented); - } - - /// - /// Gets a property from a device path by name - /// - /// The path to the device object. - /// The name of the property to retrieve. - /// The value of the property, or a JSON-formatted error string if the device or property is not found. - public static object GetPropertyByName(string deviceObjectPath, string propertyName) - { - var dev = FindObjectOnPath(deviceObjectPath); - if (dev == null) - return "{ \"error\":\"No Device\"}"; - - object prop = dev.GetType().GetProperty(propertyName).GetValue(dev, null); - - // var prop = t.GetProperty(propertyName); - if (prop != null) + /// + /// Gets a property from a device path by name + /// + /// The path to the device object + /// The name of the property to get + /// The value of the property + public static object GetPropertyByName(string deviceObjectPath, string propertyName) { - return prop; + var dev = FindObjectOnPath(deviceObjectPath); + if (dev == null) + return "{ \"error\":\"No Device\"}"; + + object prop = dev.GetType().GetProperty(propertyName).GetValue(dev, null); + + // var prop = t.GetProperty(propertyName); + if (prop != null) + { + return prop; + } + else + { + Debug.LogMessage(LogEventLevel.Debug, "Unable to find Property: {0} on Device with path: {1}", propertyName, deviceObjectPath); + return null; + } } - else + + /// + /// Gets the methods on a device + /// + /// The path to the device object + /// A JSON string representing the methods of the device + public static string GetMethods(string deviceObjectPath) { - Debug.LogMessage(LogEventLevel.Debug, "Unable to find Property: {0} on Device with path: {1}", propertyName, deviceObjectPath); - return null; + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; + + // Package up method names using helper objects + Type t = obj.GetType(); + var methods = t.GetMethods() + .Where(m => !m.IsSpecialName) + .Select(p => new MethodNameParams(p)); + return JsonConvert.SerializeObject(methods, Formatting.Indented); } - } - /// - /// Gets the methods on a device - /// - /// The path to the device object. - /// A JSON-formatted string representing the methods of the device. - public static string GetMethods(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; + /// + /// Gets the API methods on a device + /// + /// The path to the device object + /// A JSON string representing the API methods of the device + public static string GetApiMethods(string deviceObjectPath) + { + var obj = FindObjectOnPath(deviceObjectPath); + if (obj == null) + return "{ \"error\":\"No Device\"}"; - // Package up method names using helper objects - Type t = obj.GetType(); - var methods = t.GetMethods() - .Where(m => !m.IsSpecialName) - .Select(p => new MethodNameParams(p)); - return JsonConvert.SerializeObject(methods, Formatting.Indented); - } - - /// - /// Gets the API methods on a device, which are the methods marked with the ApiAttribute. - /// These are the methods intended to be called through the JSON API. - /// This allows for hiding certain methods from the API if desired. - /// - /// The path to the device object. - /// A JSON-formatted string representing the API methods of the device. - public static string GetApiMethods(string deviceObjectPath) - { - var obj = FindObjectOnPath(deviceObjectPath); - if (obj == null) - return "{ \"error\":\"No Device\"}"; - - // Package up method names using helper objects - Type t = obj.GetType(); - var methods = t.GetMethods() - .Where(m => !m.IsSpecialName) - .Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any()) - .Select(p => new MethodNameParams(p)); - return JsonConvert.SerializeObject(methods, Formatting.Indented); - } + // Package up method names using helper objects + Type t = obj.GetType(); + var methods = t.GetMethods() + .Where(m => !m.IsSpecialName) + .Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any()) + .Select(p => new MethodNameParams(p)); + return JsonConvert.SerializeObject(methods, Formatting.Indented); + } - /// - /// Walks down a dotted object path, starting with a Device, and returns the object - /// at the end of the path - /// - public static object FindObjectOnPath(string deviceObjectPath) - { - try + /// + /// FindObjectOnPath method + /// + /// The path to the device object + /// The object found at the specified path + public static object FindObjectOnPath(string deviceObjectPath) { var path = deviceObjectPath.Split('.'); @@ -406,188 +398,179 @@ public class DeviceJsonApi } return obj; } - catch (Exception e) + + /// + /// Sets a property on an object. + /// + /// The path to the device object + /// A JSON string representing the result of setting the property + public static string SetProperty(string deviceObjectPath) { - Debug.LogMessage(LogEventLevel.Error, e, "Error finding object on path {deviceObjectPath}", deviceObjectPath); - return null; + throw new NotImplementedException("This could be really useful. Finish it please"); + + //var obj = FindObjectOnPath(deviceObjectPath); + //if (obj == null) + // return "{\"error\":\"No object found\"}"; + + //Type t = obj.GetType(); + + + //// get the properties and set them into a new collection of NameType wrappers + //var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); + //return JsonConvert.SerializeObject(props, Formatting.Indented); } + + } /// - /// Sets a property on an object. + /// Represents a DeviceActionWrapper /// - /// - /// - public static string SetProperty(string deviceObjectPath) + public class DeviceActionWrapper { - throw new NotImplementedException("This could be really useful. Finish it please"); + /// + /// Gets or sets the DeviceKey + /// + public string DeviceKey { get; set; } - //var obj = FindObjectOnPath(deviceObjectPath); - //if (obj == null) - // return "{\"error\":\"No object found\"}"; - - //Type t = obj.GetType(); - - - //// get the properties and set them into a new collection of NameType wrappers - //var props = t.GetProperties().Select(p => new PropertyNameType(p, obj)); - //return JsonConvert.SerializeObject(props, Formatting.Indented); + /// + /// Gets or sets the MethodName + /// + public string MethodName { get; set; } + + /// + /// Gets or sets the Params + /// + public object[] Params { get; set; } } - -} - -/// -/// Attribute to mark methods as part of the JSON API. Only methods marked with this attribute will be returned by GetApiMethods and intended to be called through the JSON API. -/// -public class DeviceActionWrapper -{ /// - /// The key of the device to call the method on + /// Represents a PropertyNameType /// - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - /// - /// The name of the method to call - /// - [JsonProperty("methodName")] - public string MethodName { get; set; } - - /// - /// The parameters to pass to the method. This should be an array of objects matching the parameters of the method being called. If the method has no parameters, this can be omitted or set to null. - /// - [JsonProperty("params")] - public object[] Params { get; set; } -} - -/// -/// Helper class for serializing properties with their name, type, and value, and whether they can be read or written to. -/// -public class PropertyNameType -{ - private object Parent; - - /// - /// The PropertyInfo for the property being represented. This is ignored for JSON serialization. - /// - [JsonIgnore] - public PropertyInfo PropInfo { get; private set; } - - /// - /// The name of the property - /// - public string Name { get { return PropInfo.Name; } } - - /// - /// The type of the property - /// - public string Type { get { return PropInfo.PropertyType.Name; } } - - /// - /// The value of the property, or null if the property cannot be read or an error occurs when trying to read it. - /// - public string Value + public class PropertyNameType { - get + private object Parent; + + /// + /// Gets or sets the PropInfo + /// + [JsonIgnore] + public PropertyInfo PropInfo { get; private set; } + + /// + /// Gets or sets the Name + /// + public string Name { get { return PropInfo.Name; } } + + /// + /// Gets or sets the Type + /// + public string Type { get { return PropInfo.PropertyType.Name; } } + + /// + /// Gets or sets the Value + /// + public string Value { - if (PropInfo.CanRead) + get { - try + if (PropInfo.CanRead) { - return PropInfo.GetValue(Parent, null).ToString(); + try + { + return PropInfo.GetValue(Parent, null).ToString(); + } + catch (Exception) + { + return null; + } } - catch (Exception) - { + else return null; - } } - else - return null; } - } - /// - /// Indicates whether the property can be read from - /// - public bool CanRead { get { return PropInfo.CanRead; } } - /// - /// Indicates whether the property can be written to - /// - public bool CanWrite { get { return PropInfo.CanWrite; } } + /// + /// Gets or sets the CanRead + /// + public bool CanRead { get { return PropInfo.CanRead; } } + /// + /// Gets or sets the CanWrite + /// + public bool CanWrite { get { return PropInfo.CanWrite; } } - /// - /// Constructor - /// - /// - /// - public PropertyNameType(PropertyInfo info, object parent) - { - PropInfo = info; - Parent = parent; - } -} - -/// -/// Helper class for serializing methods with their name and parameters. The MethodInfo is ignored for JSON serialization. -/// -public class MethodNameParams -{ - /// - /// The MethodInfo for the method being represented. This is ignored for JSON serialization. - /// - [JsonIgnore] - public MethodInfo MethodInfo { get; private set; } - - /// - /// The name of the method - /// - public string Name { get { return MethodInfo.Name; } } - - /// - /// The parameters of the method, represented as an array of NameType objects with the parameter name and type. - /// - public IEnumerable Params - { - get + /// + /// PropertyNameType constructor + /// + /// property info + /// parent object + public PropertyNameType(PropertyInfo info, object parent) { - return MethodInfo.GetParameters().Select(p => - new NameType { Name = p.Name, Type = p.ParameterType.Name }); + PropInfo = info; + Parent = parent; } } /// - /// Constructor + /// Represents a MethodNameParams /// - /// - public MethodNameParams(MethodInfo info) + public class MethodNameParams { - MethodInfo = info; + /// + /// Gets or sets the MethodInfo + /// + [JsonIgnore] + public MethodInfo MethodInfo { get; private set; } + + /// + /// Gets or sets the Name + /// + public string Name { get { return MethodInfo.Name; } } + + /// + /// Gets or sets the Params + /// + public IEnumerable Params + { + get + { + return MethodInfo.GetParameters().Select(p => + new NameType { Name = p.Name, Type = p.ParameterType.Name }); + } + } + + /// + /// MethodNameParams constructor + /// + /// method info + public MethodNameParams(MethodInfo info) + { + MethodInfo = info; + } } -} - -/// -/// Helper class for serializing a name and type pair, used for method parameters in MethodNameParams. -/// -public class NameType -{ - /// - /// The name of the parameter - /// - public string Name { get; set; } /// - /// The type of the parameter + /// Represents a NameType /// - public string Type { get; set; } -} + public class NameType + { + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + /// + /// Gets or sets the Type + /// + public string Type { get; set; } + } -/// -/// Attribute to mark methods as part of the JSON API. Only methods marked with this attribute will be returned by GetApiMethods and intended to be called through the JSON API. -/// -[AttributeUsage(AttributeTargets.All)] -public class ApiAttribute : Attribute -{ + /// + /// Represents a ApiAttribute + /// + [AttributeUsage(AttributeTargets.All)] + public class ApiAttribute : Attribute + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs index 6266db5c..f32fe84e 100644 --- a/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs +++ b/src/PepperDash.Essentials.Core/Devices/DeviceManager.cs @@ -2,562 +2,533 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using System.Threading; using Crestron.SimplSharp; using Crestron.SimplSharpPro; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Manages devices in the system, including activation and console commands to interact with devices -/// -public static class DeviceManager +namespace PepperDash.Essentials.Core { /// - /// Event raised when all devices have been activated + /// Manages the devices in the system /// - public static event EventHandler AllDevicesActivated; - - /// - /// Event raised when all devices have been registered - /// - public static event EventHandler AllDevicesRegistered; - - /// - /// Event raised when all devices have been initialized - /// - public static event EventHandler AllDevicesInitialized; - - private static readonly object _deviceLock = new(); - - //public static List Devices { get { return _Devices; } } - //static List _Devices = new List(); - - private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Returns a copy of all the devices in a list - /// - public static List AllDevices => [.. Devices.Values]; - - /// - /// Flag to indicate whether adding devices is currently allowed. This is set to false once ActivateAll is called to prevent changes to the device list after activation. - /// - public static bool AddDeviceEnabled { get; private set; } - - /// - /// Initializes the control system by enabling device management and registering console commands. - /// - /// This method sets up the control system for device management by enabling the addition of - /// devices and registering a series of console commands for interacting with devices. These commands allow - /// operators to list device statuses, feedbacks, and managed devices, as well as perform actions such as - /// simulating communication, debugging device streams, and accessing device properties and methods. - /// The instance representing the control system to initialize. - public static void Initialize(CrestronControlSystem cs) + public static class DeviceManager { - AddDeviceEnabled = true; + /// + /// Raised when all devices have been activated + /// + public static event EventHandler AllDevicesActivated; - CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", - ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", - "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); + /// + /// Raised when all devices have been registered + /// + public static event EventHandler AllDevicesRegistered; - CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator); - } + /// + /// Raised when all devices have been initialized + /// + public static event EventHandler AllDevicesInitialized; - /// - /// Calls activate steps on all Device class items - /// - public static void ActivateAll() - { - OnAllDevicesRegistered(); + private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection(); - lock (_deviceLock) + private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true); + + private static readonly Dictionary Devices = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets the AllDevices + /// + public static List AllDevices { get { return new List(Devices.Values); } } + + /// + /// Gets or sets the AddDeviceEnabled + /// + public static bool AddDeviceEnabled; + + /// + /// Initialize method + /// + public static void Initialize(CrestronControlSystem cs) { - AddDeviceEnabled = false; - // PreActivate all devices - Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****"); - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PreActivate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****"); - Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); + AddDeviceEnabled = true; + CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "", + ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s).Replace(Environment.NewLine, "\r\n")), "devprops", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s).Replace(Environment.NewLine, "\r\n")), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s).Replace(Environment.NewLine, "\r\n")), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive", + "Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator); - // Activate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).Activate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - - Debug.LogMessage(LogEventLevel.Information, "****Activation complete****"); - Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****"); - - // PostActivate all devices - foreach (var d in Devices.Values) - { - try - { - if (d is Device) - (d as Device).PostActivate(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); - Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); - } - } - - Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****"); - - OnAllDevicesActivated(); - } // end lock - } - - private static void DeviceManager_Initialized(object sender, EventArgs e) - { - var allInitialized = Devices.Values.OfType().All(d => d.IsInitialized); - - if (allInitialized) - { - Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****"); - - OnAllDevicesInitialized(); - } - } - - private static void OnAllDevicesActivated() - { - var handler = AllDevicesActivated; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - private static void OnAllDevicesRegistered() - { - var handler = AllDevicesRegistered; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - private static void OnAllDevicesInitialized() - { - var handler = AllDevicesInitialized; - if (handler != null) - { - handler(null, new EventArgs()); - } - } - - /// - /// Calls activate on all Device class items - /// - public static void DeactivateAll() - { - lock (_deviceLock) - { - foreach (var d in Devices.Values.OfType()) - { - d.Deactivate(); - } - } - } - - //static void ListMethods(string devKey) - //{ - // var dev = GetDeviceForKey(devKey); - // if(dev != null) - // { - // var type = dev.GetType().GetType(); - // var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance); - // var sb = new StringBuilder(); - // sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length)); - // foreach (var m in methods) - // { - // sb.Append(string.Format("{0}(", m.Name)); - // var pars = m.GetParameters(); - // foreach (var p in pars) - // sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name)); - // sb.AppendLine(")"); - // } - // CrestronConsole.ConsoleCommandResponse(sb.ToString()); - // } - //} - - private static void ListDevices(string s) - { - Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count); - var sorted = Devices.Values.ToList(); - sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); - - foreach (var d in sorted) - { - var name = d is IKeyName ? (d as IKeyName).Name : "---"; - Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name); - } - } - - private static void ListDeviceFeedbacks(string devKey) - { - var dev = GetDeviceForKey(devKey); - if (dev == null) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); - return; - } - if (!(dev is IHasFeedback statusDev)) - { - Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey); - return; - } - statusDev.DumpFeedbacksToConsole(true); - } - - //static void ListDeviceCommands(string devKey) - //{ - // var dev = GetDeviceForKey(devKey); - // if (dev == null) - // { - // Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey); - // return; - // } - // Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey); - //} - - private static void ListDeviceCommStatuses(string input) - { - - foreach (var dev in Devices.Values.OfType()) - { - CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n"); - } - } - - - //static void DoDeviceCommand(string command) - //{ - // Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned"); - //} - - /// - /// Adds a device to the manager - /// - public static void AddDevice(IKeyed newDev) - { - var lockAcquired = false; - try - { - lockAcquired = Monitor.TryEnter(_deviceLock); - if (!lockAcquired) - { - Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); - return; - } - // Check for device with same key - //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); - ////// If it exists, remove or warn?? - //if (existingDevice != null) - - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); - return; - } - - if (Devices.ContainsKey(newDev.Key)) - { - Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); - return; - } - Devices.Add(newDev.Key, newDev); - //if (!(_Devices.Contains(newDev))) - // _Devices.Add(newDev); - - if (newDev is EssentialsDevice essentialsDev) - essentialsDev.Initialized += DeviceManager_Initialized; - } - finally - { - if (lockAcquired) - Monitor.Exit(_deviceLock); - } - } - - /// - /// Adds a list of devices to the manager - /// - /// - public static void AddDevice(IEnumerable devicesToAdd) - { - var lockAcquired = false; - try - { - lockAcquired = Monitor.TryEnter(_deviceLock); - if (!lockAcquired) - { - Debug.LogMessage(LogEventLevel.Information, - "Currently unable to add devices to Device Manager. Please try again"); - return; - } - if (!AddDeviceEnabled) - { - Debug.LogMessage(LogEventLevel.Information, - "All devices have been activated. Adding new devices is not allowed."); - return; - } - - foreach (var dev in devicesToAdd) - { - try - { - Devices.Add(dev.Key, dev); - } - catch (ArgumentException ex) - { - Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", - dev.Key, ex.Message, ex.StackTrace); - } - } - } - finally - { - if (lockAcquired) - Monitor.Exit(_deviceLock); - } - } - - /// - /// Removes a device from the manager - /// - /// The device to remove - public static void RemoveDevice(IKeyed newDev) - { - lock (_deviceLock) - { - if (newDev == null) - return; - if (Devices.ContainsKey(newDev.Key)) - Devices.Remove(newDev.Key); - //if (_Devices.Contains(newDev)) - // _Devices.Remove(newDev); - else - Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); - } - } - - /// - /// Returns a list of all device keys currently in the manager - /// - /// A list of device keys - /// This method provides a way to retrieve a list of all device keys currently registered in the Device Manager. It returns an enumerable collection of strings representing the keys of the devices, allowing for easy access and manipulation of the device list as needed. - public static IEnumerable GetDeviceKeys() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Keys; - } - - /// - /// Returns a list of all devices currently in the manager - /// A list of devices - public static IEnumerable GetDevices() - { - //return _Devices.Select(d => d.Key).ToList(); - return Devices.Values; - } - - /// - /// GetDeviceForKey method - /// - public static IKeyed GetDeviceForKey(string key) - { - //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - if (key != null && Devices.ContainsKey(key)) - return Devices[key]; - - return null; - } - - /// - /// GetDeviceForKey method - /// - /// - public static T GetDeviceForKey(string key) - { - //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); - if (key == null || !Devices.ContainsKey(key)) - return default; - - if (!(Devices[key] is T)) - { - Debug.LogMessage(LogEventLevel.Error, "Device with key '{0}' is not of type '{1}'", key, typeof(T).Name); - return default; + CrestronConsole.AddNewConsoleCommand(SetDeviceStreamDebugging, "setdevicestreamdebug", "set comm debug [deviceKey] [off/rx/tx/both] ([minutes])", ConsoleAccessLevelEnum.AccessOperator); + CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator); } - return (T)Devices[key]; - } - /// - /// Console handler that simulates com port data receive - /// - /// - public static void SimulateComReceiveOnDevice(string s) - { - // devcomsim:1 xyzabc - var match = Regex.Match(s, @"(\S*)\s*(.*)"); - if (match.Groups.Count < 3) - { - CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P "); - return; - } - //Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); - - if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com)) - { - CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); - return; - } - com.SimulateReceive(match.Groups[2].Value); - } - - /// - /// Prints a list of routing inputs and outputs by device key. - /// - /// Device key from which to report data - public static void GetRoutingPorts(string s) - { - var device = GetDeviceForKey(s); - - if (device == null) return; - var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null; - var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null; - if (inputPorts != null) - { - CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine); - foreach (var routingInputPort in inputPorts) - { - CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingInputPort.Key, routingInputPort.Type, CrestronEnvironment.NewLine); - } - } - if (outputPorts == null) return; - CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine); - foreach (var routingOutputPort in outputPorts) - { - CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingOutputPort.Key, routingOutputPort.Type, CrestronEnvironment.NewLine); - } - } - - /// - /// Attempts to set the debug level of a device - /// - /// - public static void SetDeviceStreamDebugging(string s) - { - if (String.IsNullOrEmpty(s) || s.Contains("?")) - { - CrestronConsole.ConsoleCommandResponse( - "SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]\r\n" + - " {deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use\r\n" + - " timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes"); - return; - } - - var args = s.Split(' '); - - var deviceKey = args[0]; - var setting = args[1]; - - var timeout = String.Empty; - - if (args.Length >= 3) - { - timeout = args[2]; - } - - - if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device)) - { - CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey); - return; - } - - eStreamDebuggingSetting debugSetting; - - try - { - debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true); - } - catch - { - CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both"); - return; - } - - if (!string.IsNullOrEmpty(timeout)) + /// + /// ActivateAll method + /// + public static void ActivateAll() { try { - var min = Convert.ToUInt32(timeout); + OnAllDevicesRegistered(); - device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min); - CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min); + DeviceCriticalSection.Enter(); + AddDeviceEnabled = false; + // PreActivate all devices + Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****"); + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).PreActivate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } + Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****"); + Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****"); + // Activate all devices + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).Activate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } + + Debug.LogMessage(LogEventLevel.Information, "****Activation complete****"); + Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****"); + + // PostActivate all devices + foreach (var d in Devices.Values) + { + try + { + if (d is Device) + (d as Device).PostActivate(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key); + Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace); + } + } + + Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****"); + + OnAllDevicesActivated(); } - catch (Exception e) + finally { - CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e); + DeviceCriticalSection.Leave(); } } - else - { - device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); - CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting); - } - } - /// - /// Sets stream debugging settings to off for all devices - /// - public static void DisableAllDeviceStreamDebugging() - { - foreach (var device in AllDevices) + private static void DeviceManager_Initialized(object sender, EventArgs e) { - if (device is IStreamDebugging streamDevice) + var allInitialized = Devices.Values.OfType().All(d => d.IsInitialized); + + if (allInitialized) { - streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off); + Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****"); + + OnAllDevicesInitialized(); + } + } + + private static void OnAllDevicesActivated() + { + var handler = AllDevicesActivated; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + private static void OnAllDevicesRegistered() + { + var handler = AllDevicesRegistered; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + private static void OnAllDevicesInitialized() + { + var handler = AllDevicesInitialized; + if (handler != null) + { + handler(null, new EventArgs()); + } + } + + /// + /// DeactivateAll method + /// + public static void DeactivateAll() + { + try + { + DeviceCriticalSection.Enter(); + foreach (var d in Devices.Values.OfType()) + { + d.Deactivate(); + } + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + private static void ListDevices(string s) + { + CrestronConsole.ConsoleCommandResponse($"{Devices.Count} Devices registered with Device Manager:\r\n"); + + var sorted = Devices.Values.ToList(); + sorted.Sort((a, b) => a.Key.CompareTo(b.Key)); + + foreach (var d in sorted) + { + var name = d is IKeyName ? (d as IKeyName).Name : "---"; + CrestronConsole.ConsoleCommandResponse($" [{d.Key}] {name}\r\n"); + } + } + + private static void ListDeviceFeedbacks(string devKey) + { + var dev = GetDeviceForKey(devKey); + if (dev == null) + { + CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' not found\r\n"); + return; + } + if (!(dev is IHasFeedback statusDev)) + { + CrestronConsole.ConsoleCommandResponse($"Device '{devKey}' does not have visible feedbacks\r\n"); + return; + } + statusDev.DumpFeedbacksToConsole(true); + } + + private static void ListDeviceCommStatuses(string input) + { + + foreach (var dev in Devices.Values.OfType()) + { + CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n"); + } + } + + /// + /// AddDevice method + /// + public static void AddDevice(IKeyed newDev) + { + try + { + if (!DeviceCriticalSection.TryEnter()) + { + Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again"); + return; + } + // Check for device with same key + //var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase)); + ////// If it exists, remove or warn?? + //if (existingDevice != null) + + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed."); + return; + } + + if (Devices.ContainsKey(newDev.Key)) + { + Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager"); + return; + } + Devices.Add(newDev.Key, newDev); + //if (!(_Devices.Contains(newDev))) + // _Devices.Add(newDev); + + if (newDev is EssentialsDevice essentialsDev) + essentialsDev.Initialized += DeviceManager_Initialized; + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + /// + /// AddDevice method + /// + public static void AddDevice(IEnumerable devicesToAdd) + { + try + { + if (!DeviceCriticalSection.TryEnter()) + { + Debug.LogMessage(LogEventLevel.Information, + "Currently unable to add devices to Device Manager. Please try again"); + return; + } + if (!AddDeviceEnabled) + { + Debug.LogMessage(LogEventLevel.Information, + "All devices have been activated. Adding new devices is not allowed."); + return; + } + + foreach (var dev in devicesToAdd) + { + try + { + Devices.Add(dev.Key, dev); + } + catch (ArgumentException ex) + { + Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}", + dev.Key, ex.Message, ex.StackTrace); + } + } + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + /// + /// RemoveDevice method + /// + public static void RemoveDevice(IKeyed newDev) + { + try + { + DeviceCriticalSection.Enter(); + if (newDev == null) + return; + if (Devices.ContainsKey(newDev.Key)) + Devices.Remove(newDev.Key); + //if (_Devices.Contains(newDev)) + // _Devices.Remove(newDev); + else + Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key); + } + finally + { + DeviceCriticalSection.Leave(); + } + } + + /// + /// GetDeviceKeys method + /// + public static IEnumerable GetDeviceKeys() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Keys; + } + + /// + /// GetDevices method + /// + public static IEnumerable GetDevices() + { + //return _Devices.Select(d => d.Key).ToList(); + return Devices.Values; + } + + /// + /// GetDeviceForKey method + /// + public static IKeyed GetDeviceForKey(string key) + { + //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key != null && Devices.ContainsKey(key)) + return Devices[key]; + + return null; + } + + /// + /// GetDeviceForKey method + /// + /// + public static T GetDeviceForKey(string key) + { + //return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (key == null || !Devices.ContainsKey(key)) + return default; + + if (!(Devices[key] is T)) + { + Debug.LogMessage(LogEventLevel.Error, "Device with key '{0}' is not of type '{1}'", key, typeof(T).Name); + return default; + } + + return (T)Devices[key]; + } + + /// + /// Console handler that simulates com port data receive + /// + /// + /// + /// SimulateComReceiveOnDevice method + /// + public static void SimulateComReceiveOnDevice(string s) + { + // devcomsim:1 xyzabc + var match = Regex.Match(s, @"(\S*)\s*(.*)"); + if (match.Groups.Count < 3) + { + CrestronConsole.ConsoleCommandResponse(" Format: devsimreceive:P "); + return; + } + //Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value); + + if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com)) + { + CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value); + return; + } + com.SimulateReceive(match.Groups[2].Value); + } + + /// + /// Prints a list of routing inputs and outputs by device key. + /// + /// Device key from which to report data + /// + /// GetRoutingPorts method + /// + public static void GetRoutingPorts(string s) + { + var device = GetDeviceForKey(s); + + if (device == null) return; + var inputPorts = ((device as IRoutingInputs) != null) ? (device as IRoutingInputs).InputPorts : null; + var outputPorts = ((device as IRoutingOutputs) != null) ? (device as IRoutingOutputs).OutputPorts : null; + if (inputPorts != null) + { + CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Input Ports:{2}", s, inputPorts.Count, CrestronEnvironment.NewLine); + foreach (var routingInputPort in inputPorts) + { + CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingInputPort.Key, routingInputPort.Type, CrestronEnvironment.NewLine); + } + } + if (outputPorts == null) return; + CrestronConsole.ConsoleCommandResponse("Device {0} has {1} Output Ports:{2}", s, outputPorts.Count, CrestronEnvironment.NewLine); + foreach (var routingOutputPort in outputPorts) + { + CrestronConsole.ConsoleCommandResponse("key: {0} signalType: {1}{2}", routingOutputPort.Key, routingOutputPort.Type, CrestronEnvironment.NewLine); + } + } + + /// + /// Attempts to set the debug level of a device + /// + /// + /// + /// SetDeviceStreamDebugging method + /// + public static void SetDeviceStreamDebugging(string s) + { + if (String.IsNullOrEmpty(s) || s.Contains("?")) + { + CrestronConsole.ConsoleCommandResponse( + "SETDEVICESTREAMDEBUG [{deviceKey}] [OFF |TX | RX | BOTH] [timeOutInMinutes]\r\n" + + " {deviceKey} [OFF | TX | RX | BOTH] - Device to set stream debugging on, and which setting to use\r\n" + + " timeOutInMinutes - Set timeout for stream debugging. Default is 30 minutes"); + return; + } + + var args = s.Split(' '); + + var deviceKey = args[0]; + var setting = args[1]; + + var timeout = String.Empty; + + if (args.Length >= 3) + { + timeout = args[2]; + } + + + if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device)) + { + CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey); + return; + } + + eStreamDebuggingSetting debugSetting; + + try + { + debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), setting, true); + } + catch + { + CrestronConsole.ConsoleCommandResponse("Unable to convert setting value. Please use off/rx/tx/both"); + return; + } + + if (!string.IsNullOrEmpty(timeout)) + { + try + { + var min = Convert.ToUInt32(timeout); + + device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, min); + CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for {2} minutes", deviceKey, debugSetting, min); + + } + catch (Exception e) + { + CrestronConsole.ConsoleCommandResponse("Unable to convert minutes or settings value. Please use an integer value for minutes. Error: {0}", e); + } + } + else + { + device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); + CrestronConsole.ConsoleCommandResponse("Device: '{0}' debug level set to {1} for default time (30 minutes)", deviceKey, debugSetting); + } + } + + /// + /// DisableAllDeviceStreamDebugging method + /// + public static void DisableAllDeviceStreamDebugging() + { + foreach (var device in AllDevices) + { + if (device is IStreamDebugging streamDevice) + { + streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off); + } } } } diff --git a/src/PepperDash.Essentials.Core/Devices/DisplayUiConstants.cs b/src/PepperDash.Essentials.Core/Devices/DisplayUiConstants.cs index 39e7067b..1c6ae244 100644 --- a/src/PepperDash.Essentials.Core/Devices/DisplayUiConstants.cs +++ b/src/PepperDash.Essentials.Core/Devices/DisplayUiConstants.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Integers that represent the "source type number" for given sources. /// Primarily used by the UI to calculate subpage join offsets @@ -62,4 +62,5 @@ namespace PepperDash.Essentials.Core; /// TypeNoControls constant /// public const uint TypeNoControls = 49; - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs index 23b06e9a..0b1a5ff7 100644 --- a/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs @@ -1,17 +1,31 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core.Bridges; -namespace PepperDash.Essentials.Core; - -public abstract class EssentialsBridgeableDevice:EssentialsDevice, IBridgeAdvanced +namespace PepperDash.Essentials.Core { - protected EssentialsBridgeableDevice(string key) : base(key) + /// + /// Base class for devices that can be bridged to an EISC API. + /// + public abstract class EssentialsBridgeableDevice : EssentialsDevice, IBridgeAdvanced { - } + /// + /// Initializes a new instance of the class with the specified key. + /// + /// The unique key for the device. + protected EssentialsBridgeableDevice(string key) : base(key) + { + } - protected EssentialsBridgeableDevice(string key, string name) : base(key, name) - { - } + /// + /// Initializes a new instance of the class with the specified key and name. + /// + /// The unique key for the device. + /// The display name for the device. + protected EssentialsBridgeableDevice(string key, string name) : base(key, name) + { + } - public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); + /// + public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs index 81cd0439..59fba681 100644 --- a/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs @@ -1,249 +1,103 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using PepperDash.Core; -using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the basic needs for an EssentialsDevice to enable it to be build by an IDeviceFactory class -/// -[Description("The base Essentials Device Class")] -public abstract class EssentialsDevice : Device +namespace PepperDash.Essentials.Core { - /// - /// Event that fires when the device has completed initialization. This is useful for any setup that needs to occur after all devices have been activated. + /// Defines the basic needs for an EssentialsDevice to enable it to be build by an IDeviceFactory class /// - public event EventHandler Initialized; - - private bool _isInitialized; - - /// - /// Indicates whether the device has completed initialization. Initialization occurs after all devices have been activated, and is triggered by the DeviceManager.AllDevicesActivated event. This property can be used to determine when it is safe to perform actions that require all devices to be active. - /// - public bool IsInitialized + [Description("The base Essentials Device Class")] + public abstract class EssentialsDevice : Device { - get { return _isInitialized; } - private set + /// + /// Event raised when the device is initialized. + /// + public event EventHandler Initialized; + + private bool _isInitialized; + + /// + /// Gets a value indicating whether the device is initialized. + /// + public bool IsInitialized { - if (_isInitialized == value) return; - - _isInitialized = value; - - if (_isInitialized) + get { return _isInitialized; } + private set { - Initialized?.Invoke(this, new EventArgs()); + if (_isInitialized == value) return; + + _isInitialized = value; + + if (_isInitialized) + { + Initialized?.Invoke(this, new EventArgs()); + } } } - } - /// - /// Constructor - /// - /// - protected EssentialsDevice(string key) - : base(key) - { - SubscribeToActivateComplete(); - } - - /// - /// Constructor - /// - /// - /// - protected EssentialsDevice(string key, string name) - : base(key, name) - { - SubscribeToActivateComplete(); - } - - private void SubscribeToActivateComplete() - { - DeviceManager.AllDevicesActivated += DeviceManagerOnAllDevicesActivated; - } - - private void DeviceManagerOnAllDevicesActivated(object sender, EventArgs eventArgs) - { - Task.Run(() => + /// + /// Initializes a new instance of the EssentialsDevice class. + /// + /// The unique identifier for the device. + protected EssentialsDevice(string key) + : base(key) { - try - { - Initialize(); - - IsInitialized = true; - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Error, this, "Exception initializing device: {0}", ex.Message); - Debug.LogMessage(LogEventLevel.Debug, this, "Stack Trace: {0}", ex.StackTrace); - } - }); - } - - /// - /// Override this method to perform any initialization that requires all devices to be activated. - /// This method is called automatically after the DeviceManager.AllDevicesActivated event is fired, - /// and should not be called directly. - /// If this is not called then no Mobile Control messengers will be created. - /// - /// - protected override bool CustomActivate() - { - CreateMobileControlMessengers(); - - return base.CustomActivate(); - } - - /// - /// Override this method to build and create custom Mobile Control Messengers during the Activation phase - /// - protected virtual void CreateMobileControlMessengers() - { - var controller = DeviceManager.AllDevices - .OfType() - .FirstOrDefault(); - - if (controller == null) - { - Debug.LogMessage( - LogEventLevel.Warning, - this, - "No IMobileControl controller found; default messengers will not be registered for {key}", - Key); - return; + SubscribeToActivateComplete(); } - controller.AddDefaultMessengersForDevice(this); - } -} - -/// -/// Provides a description for a device that can be used in the UI and other areas where a human-readable description of the device is needed. -/// -[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] -public class DescriptionAttribute : Attribute -{ - private string _Description; - - /// - /// Constructor - /// - /// - public DescriptionAttribute(string description) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Setting Description: {0}", description); - _Description = description; - } - - /// - /// Gets the description of the device - /// - public string Description - { - get { return _Description; } - } -} - -/// -/// Provides a configuration snippet for a device that can be used in the UI and other areas where a sample configuration of the device is needed. This is especially useful for plugin developers to provide users with an example of how to configure their device. -/// -[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] -public class ConfigSnippetAttribute : Attribute -{ - private string _ConfigSnippet; - - /// - /// Constructor - /// - /// - public ConfigSnippetAttribute(string configSnippet) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Setting Config Snippet {0}", configSnippet); - _ConfigSnippet = configSnippet; - } - - /// - /// Gets the configuration snippet for the device - /// - public string ConfigSnippet - { - get { return _ConfigSnippet; } - } -} - -/// -/// Provides a base class for creating device factories specifically designed for processor extension development in the Essentials framework. This class implements the IProcessorExtensionDeviceFactory interface and provides a structure for defining type names and building devices based on a DeviceConfig object. -/// -/// The type of device that this factory creates. Must derive from . -public abstract class ProcessorExtensionDeviceFactory : IProcessorExtensionDeviceFactory where T : EssentialsDevice -{ - #region IProcessorExtensionDeviceFactory Members - - /// - /// A list of strings that can be used in the type property of a DeviceConfig object to build an instance of this device - /// - public List TypeNames { get; protected set; } - - /// - /// Loads an item to the ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods dictionary for each entry in the TypeNames list - /// - public void LoadFactories() - { - foreach (var typeName in TypeNames) + /// + /// Initializes a new instance of the EssentialsDevice class. + /// + /// The unique identifier for the device. + /// The name of the device. + protected EssentialsDevice(string key, string name) + : base(key, name) { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = typeof(T).GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - ProcessorExtensionDeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice); + SubscribeToActivateComplete(); + } + + private void SubscribeToActivateComplete() + { + DeviceManager.AllDevicesActivated += DeviceManagerOnAllDevicesActivated; + } + + private void DeviceManagerOnAllDevicesActivated(object sender, EventArgs eventArgs) + { + Task.Run(() => + { + try + { + Initialize(); + + IsInitialized = true; + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Error, this, "Exception initializing device: {0}", ex.Message); + Debug.LogMessage(LogEventLevel.Debug, this, "Stack Trace: {0}", ex.StackTrace); + } + }); + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + CreateMobileControlMessengers(); + + return base.CustomActivate(); + } + + /// + /// Override this method to build and create custom Mobile Control Messengers during the Activation phase + /// + protected virtual void CreateMobileControlMessengers() + { + } } - - /// - /// The method that will build the device - /// - /// The device config - /// An instance of the device - public abstract EssentialsDevice BuildDevice(DeviceConfig dc); - - #endregion - -} - -/// -/// Devices the basic needs for a Device Factory -/// -public abstract class EssentialsPluginDeviceFactory : EssentialsDeviceFactory, IPluginDeviceFactory where T : EssentialsDevice -{ - /// - /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") - /// - public string MinimumEssentialsFrameworkVersion { get; protected set; } -} - -/// -/// Provides a base class for creating device factories specifically designed for plugin development in the Essentials -/// framework. -/// -/// This class is intended to be used by developers creating plugins for the Essentials framework. It -/// includes properties to specify the minimum required Essentials framework version and to track framework versions -/// used during development. -/// The type of device that this factory creates. Must derive from . -public abstract class EssentialsPluginDevelopmentDeviceFactory : EssentialsDeviceFactory, IPluginDevelopmentDeviceFactory where T : EssentialsDevice -{ - /// - /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") - /// - public string MinimumEssentialsFrameworkVersion { get; protected set; } - - /// - /// A list of Essentials framework versions that were used during the development of the plugin. This can be used to track which versions of the framework the plugin is compatible with, and can be helpful for users when determining whether a plugin will work with their current version of the Essentials framework. - /// - public List DevelopmentEssentialsFrameworkVersions { get; protected set; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs index 3f105628..9c1a6a94 100644 --- a/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsDeviceFactory.cs @@ -1,16 +1,14 @@ -using System; +using System; using System.Collections.Generic; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials.Core; - -/// -/// Devices the basic needs for a Device Factory -/// -public abstract class EssentialsDeviceFactory : IDeviceFactory where T:EssentialsDevice +namespace PepperDash.Essentials.Core { - #region IDeviceFactory Members - + /// + /// Provides the basic needs for a Device Factory + /// + public abstract class EssentialsDeviceFactory : IDeviceFactory where T : EssentialsDevice + { /// public Type FactoryType => typeof(T); @@ -20,11 +18,10 @@ public abstract class EssentialsDeviceFactory : IDeviceFactory where T:Essent public List TypeNames { get; protected set; } /// - /// The method that will build the device + /// Build the device using the configuration /// /// The device config /// An instance of the device public abstract EssentialsDevice BuildDevice(DeviceConfig dc); - - #endregion + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs new file mode 100644 index 00000000..8bd60124 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDevelopmentDeviceFactory.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + /// + /// EssentialsPluginDevelopmentDeviceFactory class + /// + /// + public abstract class EssentialsPluginDevelopmentDeviceFactory : EssentialsDeviceFactory, IPluginDevelopmentDeviceFactory where T : EssentialsDevice + { + /// + /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") + /// + public string MinimumEssentialsFrameworkVersion { get; protected set; } + + /// + /// Gets or sets the DevelopmentEssentialsFrameworkVersions + /// + public List DevelopmentEssentialsFrameworkVersions { get; protected set; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs new file mode 100644 index 00000000..7a891905 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/EssentialsPluginDeviceFactory.cs @@ -0,0 +1,14 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Devices the basic needs for a Device Factory + /// + public abstract class EssentialsPluginDeviceFactory : EssentialsDeviceFactory, IPluginDeviceFactory where T : EssentialsDevice + { + /// + /// Specifies the minimum version of Essentials required for a plugin to run. Must use the format Major.Minor.Build (ex. "1.4.33") + /// + public string MinimumEssentialsFrameworkVersion { get; protected set; } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs b/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs index 77bb4fc3..a55235e6 100644 --- a/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs +++ b/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs @@ -11,68 +11,84 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Bridges.JoinMaps; using Serilog.Events; -namespace PepperDash.Essentials.Core.Devices; - -public class GenericIrController: EssentialsBridgeableDevice +namespace PepperDash.Essentials.Core.Devices { - //data storage for bridging - private BasicTriList _trilist; - private uint _joinStart; - private string _joinMapKey; - private EiscApiAdvanced _bridge; - - private readonly IrOutputPortController _port; - - public string[] IrCommands {get { return _port.IrFileCommands; }} - - public GenericIrController(string key, string name, IrOutputPortController irPort) : base(key, name) + /// + /// Represents a GenericIrController + /// + public class GenericIrController: EssentialsBridgeableDevice { - _port = irPort; - if (_port == null) - { - Debug.LogMessage(LogEventLevel.Information, this, "IR Port is null, device will not function"); - return; - } - DeviceManager.AddDevice(_port); + //data storage for bridging + private BasicTriList _trilist; + private uint _joinStart; + private string _joinMapKey; + private EiscApiAdvanced _bridge; - _port.DriverLoaded.OutputChange += DriverLoadedOnOutputChange; - } + private readonly IrOutputPortController _port; - private void DriverLoadedOnOutputChange(object sender, FeedbackEventArgs args) - { - if (!args.BoolValue) + /// + /// Gets or sets the IrCommands + /// + public string[] IrCommands {get { return _port.IrFileCommands; }} + + /// + /// Constructor + /// + /// key for the device + /// name of the device + /// IR output port controller + public GenericIrController(string key, string name, IrOutputPortController irPort) : base(key, name) { - return; + _port = irPort; + if (_port == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "IR Port is null, device will not function"); + return; + } + DeviceManager.AddDevice(_port); + + _port.DriverLoaded.OutputChange += DriverLoadedOnOutputChange; } - if (_trilist == null || _bridge == null) + private void DriverLoadedOnOutputChange(object sender, FeedbackEventArgs args) { - return; + if (!args.BoolValue) + { + return; + } + + if (_trilist == null || _bridge == null) + { + return; + } + + LinkToApi(_trilist, _joinStart, _joinMapKey, _bridge); } - LinkToApi(_trilist, _joinStart, _joinMapKey, _bridge); - } + #region Overrides of EssentialsBridgeableDevice - #region Overrides of EssentialsBridgeableDevice - - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - //if driver isn't loaded yet, store the variables until it is loaded, then call the LinkToApi method again - if (!_port.DriverIsLoaded) + /// + /// LinkToApi method + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { - _trilist = trilist; - _joinStart = joinStart; - _joinMapKey = joinMapKey; - _bridge = bridge; - return; - } + //if driver isn't loaded yet, store the variables until it is loaded, then call the LinkToApi method again + if (!_port.DriverIsLoaded) + { + _trilist = trilist; + _joinStart = joinStart; + _joinMapKey = joinMapKey; + _bridge = bridge; + return; + } - var joinMap = new GenericIrControllerJoinMap(joinStart); + var joinMap = new GenericIrControllerJoinMap(joinStart); - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); if (_port.UseBridgeJoinMap) { @@ -134,42 +150,56 @@ public class GenericIrController: EssentialsBridgeableDevice } } - joinMap.PrintJoinMapInfo(); + joinMap.PrintJoinMapInfo(); - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } } - else + + #endregion + + /// + /// Press method + /// + public void Press(string command, bool pressRelease) { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + _port.PressRelease(command, pressRelease); } } - #endregion - - public void Press(string command, bool pressRelease) + /// + /// Represents a GenericIrControllerFactory + /// + public class GenericIrControllerFactory : EssentialsDeviceFactory { - _port.PressRelease(command, pressRelease); + /// + /// Constructor + /// + public GenericIrControllerFactory() + { + TypeNames = new List {"genericIrController"}; + } + #region Overrides of EssentialsDeviceFactory + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic IR Controller Device"); + + var irPort = IRPortHelper.GetIrOutputPortController(dc); + + return new GenericIrController(dc.Key, dc.Name, irPort); + } + + #endregion } -} - -public class GenericIrControllerFactory : EssentialsDeviceFactory -{ - public GenericIrControllerFactory() - { - TypeNames = new List {"genericIrController"}; - } - #region Overrides of EssentialsDeviceFactory - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic IR Controller Device"); - - var irPort = IRPortHelper.GetIrOutputPortController(dc); - - return new GenericIrController(dc.Key, dc.Name, irPort); - } - - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/GenericMonitoredTcpDevice.cs b/src/PepperDash.Essentials.Core/Devices/GenericMonitoredTcpDevice.cs index 7373f149..148a9cac 100644 --- a/src/PepperDash.Essentials.Core/Devices/GenericMonitoredTcpDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/GenericMonitoredTcpDevice.cs @@ -8,10 +8,10 @@ using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Devices; - +namespace PepperDash.Essentials.Core.Devices +{ /// - /// A device that monitors the communication status of a communication client. Useful for monitoring TCP, serial, or telnet clients. + /// Represents a GenericCommunicationMonitoredDevice /// public class GenericCommunicationMonitoredDevice : Device, ICommunicationMonitor { @@ -63,7 +63,7 @@ namespace PepperDash.Essentials.Core.Devices; /// CustomActivate method /// /// - protected override bool CustomActivate() + public override bool CustomActivate() { CommunicationMonitor.Start(); return true; @@ -77,4 +77,5 @@ namespace PepperDash.Essentials.Core.Devices; CommunicationMonitor.Stop(); return true; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatusExtensions.cs b/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatusExtensions.cs index 4d28fa18..83914f6f 100644 --- a/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatusExtensions.cs +++ b/src/PepperDash.Essentials.Core/Devices/IAttachVideoStatusExtensions.cs @@ -4,41 +4,42 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Interface for devices that can be attached to a port that provides video status information. Provides an extension method for getting that status information. -/// -public static class IAttachVideoStatusExtensions +namespace PepperDash.Essentials.Core { /// - /// Gets the VideoStatusOutputs for the device + /// IAttachVideoStatusExtensions class /// - /// - /// Attached VideoStatusOutputs or the default if none attached - public static VideoStatusOutputs GetVideoStatuses(this IAttachVideoStatus attachedDev) + public static class IAttachVideoStatusExtensions { - // See if this device is connected to a status-providing port - var tl = TieLineCollection.Default.FirstOrDefault(t => - t.SourcePort.ParentDevice == attachedDev - && t.DestinationPort is RoutingInputPortWithVideoStatuses); - if (tl != null) + /// + /// Gets the VideoStatusOutputs for the device + /// + /// + /// Attached VideoStatusOutputs or the default if none attached + public static VideoStatusOutputs GetVideoStatuses(this IAttachVideoStatus attachedDev) { - // if so, and it's got status, return it -- or null - var port = tl.DestinationPort as RoutingInputPortWithVideoStatuses; - if (port != null) - return port.VideoStatus; + // See if this device is connected to a status-providing port + var tl = TieLineCollection.Default.FirstOrDefault(t => + t.SourcePort.ParentDevice == attachedDev + && t.DestinationPort is RoutingInputPortWithVideoStatuses); + if (tl != null) + { + // if so, and it's got status, return it -- or null + var port = tl.DestinationPort as RoutingInputPortWithVideoStatuses; + if (port != null) + return port.VideoStatus; + } + return VideoStatusOutputs.NoStatus; } - return VideoStatusOutputs.NoStatus; - } - /// - /// HasVideoStatuses method - /// - public static bool HasVideoStatuses(this IAttachVideoStatus attachedDev) - { - return TieLineCollection.Default.FirstOrDefault(t => - t.SourcePort.ParentDevice == attachedDev - && t.DestinationPort is RoutingInputPortWithVideoStatuses) != null; + /// + /// HasVideoStatuses method + /// + public static bool HasVideoStatuses(this IAttachVideoStatus attachedDev) + { + return TieLineCollection.Default.FirstOrDefault(t => + t.SourcePort.ParentDevice == attachedDev + && t.DestinationPort is RoutingInputPortWithVideoStatuses) != null; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IDspPresets.cs b/src/PepperDash.Essentials.Core/Devices/IDspPresets.cs index 96aeff92..bfe81658 100644 --- a/src/PepperDash.Essentials.Core/Devices/IDspPresets.cs +++ b/src/PepperDash.Essentials.Core/Devices/IDspPresets.cs @@ -6,7 +6,7 @@ namespace PepperDash.Essentials.Core /// /// Defines the contract for IDspPresets /// - public interface IDspPresets : IKeyed + public interface IDspPresets { /// /// Gets the Presets diff --git a/src/PepperDash.Essentials.Core/Devices/IDspPresetsDirectRecall.cs b/src/PepperDash.Essentials.Core/Devices/IDspPresetsDirectRecall.cs deleted file mode 100644 index 1e8c0b5c..00000000 --- a/src/PepperDash.Essentials.Core/Devices/IDspPresetsDirectRecall.cs +++ /dev/null @@ -1,18 +0,0 @@ -using PepperDash.Core; -using System.Collections.Generic; - -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IDspPresetsDirectRecall -/// -public interface IDspPresetsDirectRecall : IKeyed -{ - - /// - /// Recalls the preset by name - /// - /// - void RunPreset(string name); -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs b/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs new file mode 100644 index 00000000..c628129e --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/IHasDspPresetSave.cs @@ -0,0 +1,14 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IHasDspPresetSave + /// + public interface IHasDspPresetSave : IDspPresets + { + /// + /// Saves the preset by key + /// + /// key of preset to save + void SavePreset(string key); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IHasFeedbacks.cs b/src/PepperDash.Essentials.Core/Devices/IHasFeedbacks.cs index 9752d0e7..330538e0 100644 --- a/src/PepperDash.Essentials.Core/Devices/IHasFeedbacks.cs +++ b/src/PepperDash.Essentials.Core/Devices/IHasFeedbacks.cs @@ -48,40 +48,42 @@ namespace PepperDash.Essentials.Core /// public static void DumpFeedbacksToConsole(this IHasFeedback source, bool getCurrentStates) { - Type t = source.GetType(); - // get the properties and set them into a new collection of NameType wrappers - var props = t.GetProperties().Select(p => new PropertyNameType(p, t)); - var feedbacks = source.Feedbacks; if (feedbacks == null || feedbacks.Count == 0) { - Debug.LogMessage(LogEventLevel.Information, source, "\n\nAvailable feedbacks:"); - foreach (var f in feedbacks) + CrestronConsole.ConsoleCommandResponse("No available feedbacks\r\n"); + return; + } + + CrestronConsole.ConsoleCommandResponse("Available feedbacks:\r\n"); + + // Sort feedbacks by type first, then by key + var sortedFeedbacks = feedbacks.OrderBy(f => GetFeedbackTypeName(f)).ThenBy(f => string.IsNullOrEmpty(f.Key) ? "" : f.Key); + + foreach (var feedback in sortedFeedbacks) + { + string value = ""; + string type = ""; + if (getCurrentStates) { - string val = ""; - string type = ""; - if (getCurrentStates) + if (feedback is BoolFeedback) { - if (f is BoolFeedback) - { - val = f.BoolValue.ToString(); - type = "boolean"; - } - else if (f is IntFeedback) - { - val = f.IntValue.ToString(); - type = "integer"; - } - else if (f is StringFeedback) - { - val = f.StringValue; - type = "string"; - } + value = feedback.BoolValue.ToString(); + type = "boolean"; + } + else if (feedback is IntFeedback) + { + value = feedback.IntValue.ToString(); + type = "integer"; + } + else if (feedback is StringFeedback) + { + value = feedback.StringValue; + type = "string"; } - Debug.LogMessage(LogEventLevel.Information, "{0,-12} {1, -25} {2}", type, - (string.IsNullOrEmpty(f.Key) ? "-no key-" : f.Key), val); } + CrestronConsole.ConsoleCommandResponse($" {type,-12} {(string.IsNullOrEmpty(feedback.Key) ? "-no key-" : feedback.Key),-25} {value}\r\n"); } } } diff --git a/src/PepperDash.Essentials.Core/Devices/IProjectorInterfaces.cs b/src/PepperDash.Essentials.Core/Devices/IProjectorInterfaces.cs index d43c6e3b..d6473e64 100644 --- a/src/PepperDash.Essentials.Core/Devices/IProjectorInterfaces.cs +++ b/src/PepperDash.Essentials.Core/Devices/IProjectorInterfaces.cs @@ -1,34 +1,41 @@ -namespace PepperDash.Essentials.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; -/// -/// Defines the contract for IBasicVideoMute -/// -public interface IBasicVideoMute +namespace PepperDash.Essentials.Core { /// - /// Toggles the video mute + /// Defines the contract for IBasicVideoMute /// - void VideoMuteToggle(); -} - -/// -/// Defines the contract for IBasicVideoMuteWithFeedback -/// -public interface IBasicVideoMuteWithFeedback : IBasicVideoMute -{ - /// - /// Gets the VideoMuteIsOn feedback - /// - BoolFeedback VideoMuteIsOn { get; } + public interface IBasicVideoMute + { + /// + /// Toggles the video mute + /// + void VideoMuteToggle(); + } /// - /// Sets the video mute on + /// Defines the contract for IBasicVideoMuteWithFeedback /// - void VideoMuteOn(); + public interface IBasicVideoMuteWithFeedback : IBasicVideoMute + { + /// + /// Gets the VideoMuteIsOn feedback + /// + BoolFeedback VideoMuteIsOn { get; } - /// - /// Sets the video mute off - /// - void VideoMuteOff(); + /// + /// Sets the video mute on + /// + void VideoMuteOn(); -} + /// + /// Sets the video mute off + /// + void VideoMuteOff(); + + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IReconfigurableDevice.cs b/src/PepperDash.Essentials.Core/Devices/IReconfigurableDevice.cs index 7dca4f32..245c0a5c 100644 --- a/src/PepperDash.Essentials.Core/Devices/IReconfigurableDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/IReconfigurableDevice.cs @@ -6,27 +6,27 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials.Core.Devices; - - -/// -/// Defines the contract for IReconfigurableDevice -/// -public interface IReconfigurableDevice +namespace PepperDash.Essentials.Core.Devices { /// - /// Event fired when the configuration changes + /// Defines the contract for IReconfigurableDevice /// - event EventHandler ConfigChanged; + public interface IReconfigurableDevice + { + /// + /// Event fired when the configuration changes + /// + event EventHandler ConfigChanged; - /// - /// Gets the current DeviceConfig - /// - DeviceConfig Config { get; } + /// + /// Gets the current DeviceConfig + /// + DeviceConfig Config { get; } - /// - /// Sets the DeviceConfig - /// - /// config to set - void SetConfig(DeviceConfig config); -} + /// + /// Sets the DeviceConfig + /// + /// config to set + void SetConfig(DeviceConfig config); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IUsageTracking.cs b/src/PepperDash.Essentials.Core/Devices/IUsageTracking.cs index 4a45f0cc..7edce646 100644 --- a/src/PepperDash.Essentials.Core/Devices/IUsageTracking.cs +++ b/src/PepperDash.Essentials.Core/Devices/IUsageTracking.cs @@ -6,99 +6,143 @@ using Crestron.SimplSharp; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -public interface IUsageTracking +namespace PepperDash.Essentials.Core { - UsageTracking UsageTracker { get; set; } -} - -//public static class IUsageTrackingExtensions -//{ -// public static void EnableUsageTracker(this IUsageTracking device) -// { -// device.UsageTracker = new UsageTracking(); -// } -//} - -public class UsageTracking -{ - public event EventHandler DeviceUsageEnded; - - public InUseTracking InUseTracker { get; protected set; } - - public bool UsageIsTracked { get; set; } - - public bool UsageTrackingStarted { get; protected set; } - public DateTime UsageStartTime { get; protected set; } - public DateTime UsageEndTime { get; protected set; } - - public Device Parent { get; private set; } - - public UsageTracking(Device parent) + /// + /// Defines the contract for IUsageTracking + /// + public interface IUsageTracking { - Parent = parent; - - InUseTracker = new InUseTracking(); - - InUseTracker.InUseFeedback.OutputChange += InUseFeedback_OutputChange; //new EventHandler(); - } - - void InUseFeedback_OutputChange(object sender, EventArgs e) - { - if(InUseTracker.InUseFeedback.BoolValue) - { - StartDeviceUsage(); - } - else - { - EndDeviceUsage(); - } + /// + /// Gets or sets the UsageTracker + /// + UsageTracking UsageTracker { get; set; } } + //public static class IUsageTrackingExtensions + //{ + // public static void EnableUsageTracker(this IUsageTracking device) + // { + // device.UsageTracker = new UsageTracking(); + // } + //} /// - /// Stores the usage start time + /// Represents a UsageTracking /// - public void StartDeviceUsage() + public class UsageTracking { - UsageTrackingStarted = true; - UsageStartTime = DateTime.Now; - } + /// + /// Event fired when device usage ends + /// + public event EventHandler DeviceUsageEnded; - /// - /// Calculates the difference between the usage start and end times, gets the total minutes used and fires an event to pass that info to a consumer - /// - public void EndDeviceUsage() - { - try + /// + /// Gets or sets the InUseTracker + /// + public InUseTracking InUseTracker { get; protected set; } + + /// + /// Gets or sets the UsageIsTracked + /// + public bool UsageIsTracked { get; set; } + + /// + /// Gets or sets the UsageTrackingStarted + /// + public bool UsageTrackingStarted { get; protected set; } + /// + /// Gets or sets the UsageStartTime + /// + public DateTime UsageStartTime { get; protected set; } + /// + /// Gets or sets the UsageEndTime + /// + public DateTime UsageEndTime { get; protected set; } + + /// + /// Gets or sets the Parent + /// + public Device Parent { get; private set; } + + /// + /// Constructor for UsageTracking class + /// + /// The parent device + public UsageTracking(Device parent) { - UsageTrackingStarted = false; + Parent = parent; + + InUseTracker = new InUseTracking(); - UsageEndTime = DateTime.Now; + InUseTracker.InUseFeedback.OutputChange += InUseFeedback_OutputChange; //new EventHandler(); + } - if (UsageStartTime != null) + void InUseFeedback_OutputChange(object sender, EventArgs e) + { + if(InUseTracker.InUseFeedback.BoolValue) { - var timeUsed = UsageEndTime - UsageStartTime; - - var handler = DeviceUsageEnded; - - if (handler != null) - { - Debug.LogMessage(LogEventLevel.Debug, "Device Usage Ended for: {0} at {1}. In use for {2} minutes.", Parent.Name, UsageEndTime, timeUsed.Minutes); - handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); - } + StartDeviceUsage(); + } + else + { + EndDeviceUsage(); } } - catch (Exception e) + + + /// + /// StartDeviceUsage method + /// + public void StartDeviceUsage() { - Debug.LogMessage(LogEventLevel.Debug, "Error ending device usage: {0}", e); + UsageTrackingStarted = true; + UsageStartTime = DateTime.Now; + } + + /// + /// Calculates the difference between the usage start and end times, gets the total minutes used and fires an event to pass that info to a consumer + /// + public void EndDeviceUsage() + { + try + { + UsageTrackingStarted = false; + + UsageEndTime = DateTime.Now; + + if (UsageStartTime != null) + { + var timeUsed = UsageEndTime - UsageStartTime; + + var handler = DeviceUsageEnded; + + if (handler != null) + { + Debug.LogMessage(LogEventLevel.Debug, "Device Usage Ended for: {0} at {1}. In use for {2} minutes.", Parent.Name, UsageEndTime, timeUsed.Minutes); + handler(this, new DeviceUsageEventArgs() { UsageEndTime = UsageEndTime, MinutesUsed = timeUsed.Minutes }); + } + } + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, "Error ending device usage: {0}", e); + } } } -} -public class DeviceUsageEventArgs : EventArgs -{ - public DateTime UsageEndTime { get; set; } - public int MinutesUsed { get; set; } + /// + /// Represents a DeviceUsageEventArgs + /// + public class DeviceUsageEventArgs : EventArgs + { + /// + /// Gets or sets the UsageEndTime + /// + public DateTime UsageEndTime { get; set; } + /// + /// Gets or sets the MinutesUsed + /// + public int MinutesUsed { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs b/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs index feb6c126..1f122735 100644 --- a/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs +++ b/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs @@ -1,223 +1,232 @@ -using System; + + +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; using Newtonsoft.Json.Linq; using PepperDash.Essentials.Core.Config; + + using PepperDash.Core; using Serilog.Events; using System.IO; using PepperDash.Core.Logging; -namespace PepperDash.Essentials.Core; - - -/// -/// IR port wrapper. May act standalone -/// -public class IrOutputPortController : Device +namespace PepperDash.Essentials.Core { - uint IrPortUid; - IROutputPort IrPort; /// - /// Gets the DriverLoaded feedback + /// IR port wrapper. May act standalone /// - public BoolFeedback DriverLoaded { get; private set; } + public class IrOutputPortController : Device + { + uint IrPortUid; + IROutputPort IrPort; - /// - /// Gets or sets the StandardIrPulseTime - /// - public ushort StandardIrPulseTime { get; set; } + /// + /// Gets the DriverLoaded feedback + /// + public BoolFeedback DriverLoaded { get; private set; } - /// - /// Gets or sets the DriverFilepath - /// - public string DriverFilepath { get; private set; } + /// + /// Gets or sets the StandardIrPulseTime + /// + public ushort StandardIrPulseTime { get; set; } - /// - /// Gets or sets the DriverIsLoaded - /// - public bool DriverIsLoaded { get; private set; } + /// + /// Gets or sets the DriverFilepath + /// + public string DriverFilepath { get; private set; } - /// - /// Gets or sets the IrFileCommands - /// - public string[] IrFileCommands { get { return IrPort.AvailableStandardIRCmds(IrPortUid); } } + /// + /// Gets or sets the DriverIsLoaded + /// + public bool DriverIsLoaded { get; private set; } - /// - /// Gets or sets the UseBridgeJoinMap - /// - public bool UseBridgeJoinMap { get; private set; } + /// + /// Gets or sets the IrFileCommands + /// + public string[] IrFileCommands { get { return IrPort.AvailableStandardIRCmds(IrPortUid); } } - /// - /// Constructor for IrDevice base class. If a null port is provided, this class will - /// still function without trying to talk to a port. - /// - public IrOutputPortController(string key, IROutputPort port, string irDriverFilepath) - : base(key) - { - //if (port == null) throw new ArgumentNullException("port"); + /// + /// Gets or sets the UseBridgeJoinMap + /// + public bool UseBridgeJoinMap { get; private set; } - DriverLoaded = new BoolFeedback(() => DriverIsLoaded); - IrPort = port; - if (port == null) + /// + /// Constructor for IrDevice base class. If a null port is provided, this class will + /// still function without trying to talk to a port. + /// + public IrOutputPortController(string key, IROutputPort port, string irDriverFilepath) + : base(key) { - Debug.LogMessage(LogEventLevel.Information, this, "WARNING No valid IR Port assigned to controller. IR will not function"); - return; - } - LoadDriver(irDriverFilepath); - } + //if (port == null) throw new ArgumentNullException("port"); - /// - /// Constructor for IrDevice base class using post activation function to get port - /// - /// key of the device - /// function to call post activation - /// config of the device - public IrOutputPortController(string key, Func postActivationFunc, - DeviceConfig config) - : base(key) - { - DriverLoaded = new BoolFeedback(() => DriverIsLoaded); - UseBridgeJoinMap = config.Properties["control"].Value("useBridgeJoinMap"); - AddPostActivationAction(() => - { - IrPort = postActivationFunc(config); - - if (IrPort == null) + DriverLoaded = new BoolFeedback(() => DriverIsLoaded); + IrPort = port; + if (port == null) { Debug.LogMessage(LogEventLevel.Information, this, "WARNING No valid IR Port assigned to controller. IR will not function"); return; } + LoadDriver(irDriverFilepath); + } - // var filePath = Global.FilePathPrefix + "ir" + Global.DirectorySeparator + config.Properties["control"]["irFile"].Value(); + /// + /// Constructor for IrDevice base class using post activation function to get port + /// + /// key of the device + /// function to call post activation + /// config of the device + public IrOutputPortController(string key, Func postActivationFunc, + DeviceConfig config) + : base(key) + { + DriverLoaded = new BoolFeedback(() => DriverIsLoaded); + UseBridgeJoinMap = config.Properties["control"].Value("useBridgeJoinMap"); + AddPostActivationAction(() => + { + IrPort = postActivationFunc(config); - var fileName = config.Properties["control"]["irFile"].Value(); + if (IrPort == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "WARNING No valid IR Port assigned to controller. IR will not function"); + return; + } + + // var filePath = Global.FilePathPrefix + "ir" + Global.DirectorySeparator + config.Properties["control"]["irFile"].Value(); - var files = Directory.GetFiles(Global.FilePathPrefix, fileName, SearchOption.AllDirectories); + var fileName = config.Properties["control"]["irFile"].Value(); - if (files.Length == 0) + var files = Directory.GetFiles(Global.FilePathPrefix, fileName, SearchOption.AllDirectories); + + if(files.Length == 0) + { + this.LogError("IR file {fileName} not found in {path}", fileName, Global.FilePathPrefix); + return; + } + + if(files.Length > 1) + { + this.LogError("IR file {fileName} found in multiple locations: {files}", fileName, files); + return; + } + + var filePath = files[0]; + + Debug.LogMessage(LogEventLevel.Debug, "*************Attempting to load IR file: {0}***************", filePath); + + LoadDriver(filePath); + + PrintAvailableCommands(); + }); + } + + /// + /// PrintAvailableCommands method + /// + public void PrintAvailableCommands() + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Available IR Commands in IR File {0}", IrPortUid); + foreach (var cmd in IrPort.AvailableIRCmds()) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", cmd); + } + } + + + /// + /// Loads the IR driver at path + /// + /// path of the IR driver file + public void LoadDriver(string path) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "***Loading IR File***"); + if (string.IsNullOrEmpty(path)) path = DriverFilepath; + try + { + IrPortUid = IrPort.LoadIRDriver(path); + DriverFilepath = path; + StandardIrPulseTime = 200; + DriverIsLoaded = true; + + DriverLoaded.FireUpdate(); + } + catch { - this.LogError("IR file {fileName} not found in {path}", fileName, Global.FilePathPrefix); + DriverIsLoaded = false; + var message = string.Format("WARNING IR Driver '{0}' failed to load", path); + Debug.LogMessage(LogEventLevel.Information, this, message); + DriverLoaded.FireUpdate(); + } + } + + + /// + /// PressRelease method + /// + /// IR command to send + /// true to press, false to release + /// + public virtual void PressRelease(string command, bool state) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "IR:'{0}'={1}", command, state); + if (IrPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING No IR Port assigned to controller"); + return; + } + if (!DriverIsLoaded) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING IR driver is not loaded"); return; } - - if (files.Length > 1) + if (state) { - this.LogError("IR file {fileName} found in multiple locations: {files}", fileName, files); + if (IrPort.IsIRCommandAvailable(IrPortUid, command)) + IrPort.Press(IrPortUid, command); + else + NoIrCommandError(command); + } + else + IrPort.Release(); + } + + /// + /// Pulse method + /// + /// IR command to send + /// time to pulse the command + /// + public virtual void Pulse(string command, ushort time) + { + if (IrPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING No IR Port assigned to controller"); + return; + } + if (!DriverIsLoaded) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING IR driver is not loaded"); return; } - - var filePath = files[0]; - - Debug.LogMessage(LogEventLevel.Debug, "*************Attempting to load IR file: {0}***************", filePath); - - LoadDriver(filePath); - - PrintAvailableCommands(); - }); - } - - /// - /// PrintAvailableCommands method - /// - public void PrintAvailableCommands() - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Available IR Commands in IR File {0}", IrPortUid); - foreach (var cmd in IrPort.AvailableIRCmds()) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", cmd); - } - } - - - /// - /// Loads the IR driver at path - /// - /// path of the IR driver file - public void LoadDriver(string path) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "***Loading IR File***"); - if (string.IsNullOrEmpty(path)) path = DriverFilepath; - try - { - IrPortUid = IrPort.LoadIRDriver(path); - DriverFilepath = path; - StandardIrPulseTime = 200; - DriverIsLoaded = true; - - DriverLoaded.FireUpdate(); - } - catch - { - DriverIsLoaded = false; - var message = string.Format("WARNING IR Driver '{0}' failed to load", path); - Debug.LogMessage(LogEventLevel.Information, this, message); - DriverLoaded.FireUpdate(); - } - } - - - /// - /// PressRelease method - /// - /// IR command to send - /// true to press, false to release - /// - public virtual void PressRelease(string command, bool state) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "IR:'{0}'={1}", command, state); - if (IrPort == null) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING No IR Port assigned to controller"); - return; - } - if (!DriverIsLoaded) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING IR driver is not loaded"); - return; - } - if (state) - { if (IrPort.IsIRCommandAvailable(IrPortUid, command)) - IrPort.Press(IrPortUid, command); + IrPort.PressAndRelease(IrPortUid, command, time); else NoIrCommandError(command); } - else - IrPort.Release(); - } - /// - /// Pulse method - /// - /// IR command to send - /// time to pulse the command - /// - public virtual void Pulse(string command, ushort time) - { - if (IrPort == null) + /// + /// Notifies the console when a bad command is used. + /// + /// command that was not found + protected void NoIrCommandError(string command) { - Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING No IR Port assigned to controller"); - return; + Debug.LogMessage(LogEventLevel.Verbose, this, "Device {0}: IR Driver {1} does not contain command {2}", + Key, IrPort.IRDriverFileNameByIRDriverId(IrPortUid), command); } - if (!DriverIsLoaded) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "WARNING IR driver is not loaded"); - return; - } - if (IrPort.IsIRCommandAvailable(IrPortUid, command)) - IrPort.PressAndRelease(IrPortUid, command, time); - else - NoIrCommandError(command); - } - - /// - /// Notifies the console when a bad command is used. - /// - /// command that was not found - protected void NoIrCommandError(string command) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Device {0}: IR Driver {1} does not contain command {2}", - Key, IrPort.IRDriverFileNameByIRDriverId(IrPortUid), command); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs b/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs index a4ca71c0..821a0c2b 100644 --- a/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/LevelControlListItem.cs @@ -1,115 +1,113 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading.Tasks; using Newtonsoft.Json; using PepperDash.Core; +using PepperDash.Essentials.Core.Devices; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a level control item in a list, which can be used to control volume or mute functionality. -/// -public class LevelControlListItem : AudioControlListItemBase +namespace PepperDash.Essentials.Core { - /// - /// A reference to the IBasicVolumeWithFeedback device for control. + /// Represents a level control item in a list, which can be used to control volume or mute functionality. /// - [JsonIgnore] - public IBasicVolumeWithFeedback LevelControl + public class LevelControlListItem : AudioControlListItemBase { - get - { - if (_levelControl == null) - _levelControl = DeviceManager.GetDeviceForKey(DeviceKey) as IBasicVolumeWithFeedback; - return _levelControl; - } - } - IBasicVolumeWithFeedback _levelControl; - /// - /// Gets the name from the device if it implements IKeyName or else returns the Name property - /// - [JsonProperty("preferredName")] - public string PreferredName - { - get + /// + /// A reference to the IBasicVolumeWithFeedback device for control. + /// + [JsonIgnore] + public IBasicVolumeWithFeedback LevelControl { - if (!string.IsNullOrEmpty(Name)) return Name; - else + get { - if (LevelControl is IKeyName namedLevelControl) + if (_levelControl == null) + _levelControl = DeviceManager.GetDeviceForKey(ParentDeviceKey) as IBasicVolumeWithFeedback; + return _levelControl; + } + } + IBasicVolumeWithFeedback _levelControl; + + /// + /// Gets the name from the device if it implements IKeyName or else returns the Name property + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (!string.IsNullOrEmpty(Name)) return Name; + else { - if (namedLevelControl == null) - return "---"; - return namedLevelControl.Name; + if (LevelControl is IKeyName namedLevelControl) + { + if (namedLevelControl == null) + return "---"; + return namedLevelControl.Name; + } + else return "---"; } - else return "---"; } } - } - /// - /// The key of the device in the DeviceManager for control - /// - [JsonProperty("deviceKey")] - public string DeviceKey - { - get + /// + /// The key of the device in the DeviceManager for control + /// + [JsonProperty("deviceKey")] + public string DeviceKey { - if (string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey; - else + get { - return DeviceManager.AllDevices. - Where(d => d.Key.Contains(ParentDeviceKey) && d.Key.Contains(ItemKey)).FirstOrDefault()?.Key ?? $"{ParentDeviceKey}--{ItemKey}"; + if (string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey; + else + { + return DeviceManager.AllDevices. + Where(d => d.Key.Contains(ParentDeviceKey) && d.Key.Contains(ItemKey)).FirstOrDefault()?.Key ?? $"{ParentDeviceKey}--{ItemKey}"; + } } } + + /// + /// Indicates if the item is a level, mute , or both + /// + [JsonProperty("type")] + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public eLevelControlType Type { get; set; } + + + /// + /// Indicates if the item is a mic or not. + /// + [JsonProperty("isMic", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsMic { get; set; } + + /// + /// Indicates if the item should show the raw level in the UI. + /// + [JsonProperty("showRawLevel", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowRawLevel { get; set; } } /// - /// Indicates if the item is a level, mute , or both + /// Indicates the type of level control item. /// - [JsonProperty("type")] - [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - public eLevelControlType Type { get; set; } + [Flags] + public enum eLevelControlType + { + /// + /// Indicates that the item is a level control only + /// + Level = 1, + /// + /// Indicates that the item is a mute control only + /// + Mute = 2, + /// + /// Indicates that the item is both a level and mute control + /// + LevelAndMute = Level | Mute, + } - - /// - /// Indicates if the item is a mic or not. - /// - [JsonProperty("isMic", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsMic { get; set; } - - /// - /// Indicates if the item should show the raw level in the UI. - /// - [JsonProperty("showRawLevel", NullValueHandling = NullValueHandling.Ignore)] - public bool? ShowRawLevel { get; set; } } - - -/// -/// Indicates the type of level control item. -/// -[Flags] -public enum eLevelControlType -{ - /// - /// Indicates that the item is a level control only - /// - Level = 1, - /// - /// Indicates that the item is a mute control only - /// - Mute = 2, - /// - /// Indicates that the item is both a level and mute control - /// - LevelAndMute = Level | Mute, -} - - - - - - - diff --git a/src/PepperDash.Essentials.Core/Devices/PduInterfaces.cs b/src/PepperDash.Essentials.Core/Devices/PduInterfaces.cs index 752ff3ac..1c0431b5 100644 --- a/src/PepperDash.Essentials.Core/Devices/PduInterfaces.cs +++ b/src/PepperDash.Essentials.Core/Devices/PduInterfaces.cs @@ -4,32 +4,33 @@ using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Devices; - -/// -/// Interface for any device that is able to control its power and has a configurable reboot time -/// -public interface IHasPowerCycle : IKeyName, IHasPowerControlWithFeedback +namespace PepperDash.Essentials.Core.Devices { /// - /// Delay between power off and power on for reboot - /// - int PowerCycleTimeMs { get;} + /// Interface for any device that is able to control it'spower and has a configurable reboot time + /// + public interface IHasPowerCycle : IKeyName, IHasPowerControlWithFeedback + { + /// + /// Delay between power off and power on for reboot + /// + int PowerCycleTimeMs { get;} + + /// + /// Reboot outlet + /// + void PowerCycle(); + } /// - /// Reboot outlet - /// - void PowerCycle(); -} - -/// -/// Interface for any device that contains a collection of IHasPowerReboot Devices -/// -public interface IHasControlledPowerOutlets : IKeyName -{ - /// - /// Collection of IPduOutlets - /// - ReadOnlyDictionary PduOutlets { get; } + /// Interface for any device that contains a collection of IHasPowerReboot Devices + /// + public interface IHasControlledPowerOutlets : IKeyName + { + /// + /// Collection of IPduOutlets + /// + ReadOnlyDictionary PduOutlets { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/PowerInterfaces.cs b/src/PepperDash.Essentials.Core/Devices/PowerInterfaces.cs index a4f772d3..ef0173d5 100644 --- a/src/PepperDash.Essentials.Core/Devices/PowerInterfaces.cs +++ b/src/PepperDash.Essentials.Core/Devices/PowerInterfaces.cs @@ -1,150 +1,153 @@ using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for IHasBatteryStats -/// -public interface IHasBatteryStats : IKeyName +namespace PepperDash.Essentials.Core { /// - /// Gets the BatteryPercentage + /// Defines the contract for IHasBatteryStats /// - int BatteryPercentage { get; } + public interface IHasBatteryStats : IKeyName + { + /// + /// Gets the BatteryPercentage + /// + int BatteryPercentage { get; } + + /// + /// Gets the BatteryCautionThresholdPercentage + /// + int BatteryCautionThresholdPercentage { get; } + + /// + /// Gets the BatteryWarningThresholdPercentage + /// + int BatteryWarningThresholdPercentage { get; } + + /// + /// Gets the BatteryIsWarningFeedback + /// + BoolFeedback BatteryIsWarningFeedback { get; } + + /// + /// Gets the BatteryIsCautionFeedback + /// + BoolFeedback BatteryIsCautionFeedback { get; } + + /// + /// Gets the BatteryIsOkFeedback + /// + BoolFeedback BatteryIsOkFeedback { get; } + + /// + /// Gets the BatteryPercentageFeedback + /// + IntFeedback BatteryPercentageFeedback { get; } + } /// - /// Gets the BatteryCautionThresholdPercentage + /// Defines the contract for IHasBatteryCharging /// - int BatteryCautionThresholdPercentage { get; } + public interface IHasBatteryCharging : IHasBatteryStats + { + /// + /// Gets the BatteryIsCharging + /// + BoolFeedback BatteryIsCharging { get; } + } /// - /// Gets the BatteryWarningThresholdPercentage + /// Interface for any device that has multiple batteries that can be monitored /// - int BatteryWarningThresholdPercentage { get; } + public interface IHasBatteries : IKeyName + { + /// + /// Collection of batteries + /// + ReadOnlyDictionary Batteries { get; } + } /// - /// Gets the BatteryIsWarningFeedback + /// Defines the contract for IHasBatteryStatsExtended /// - BoolFeedback BatteryIsWarningFeedback { get; } + public interface IHasBatteryStatsExtended : IHasBatteryStats + { + /// + /// Gets the InputVoltage in millivolts + /// + int InputVoltage { get; } + + /// + /// Gets the OutputVoltage in millivolts + /// + int OutputVoltage { get; } + + /// + /// Gets the InputCurrent in milliamps + /// + int InptuCurrent { get; } + + /// + /// Gets the OutputCurrent in milliamps + /// + int OutputCurrent { get; } + + /// + /// Gets the InputVoltageFeedback + /// + IntFeedback InputVoltageFeedback { get; } + + /// + /// Gets the OutputVoltageFeedback + /// + IntFeedback OutputVoltageFeedback { get; } + + /// + /// Gets the InputCurrentFeedback + /// + IntFeedback InputCurrentFeedback { get; } + + /// + /// Gets the OutputCurrentFeedback + /// + IntFeedback OutputCurrentFeedback { get; } + } /// - /// Gets the BatteryIsCautionFeedback + /// Defines the contract for IHasPowerCycleWithBattery /// - BoolFeedback BatteryIsCautionFeedback { get; } + public interface IHasPowerCycleWithBattery : IHasPowerCycle, IHasBatteryStats + { + + } /// - /// Gets the BatteryIsOkFeedback + /// Interface for any device that is able to control it's power and has a configurable reboot time /// - BoolFeedback BatteryIsOkFeedback { get; } + public interface IHasPowerCycle : IKeyName, IHasPowerControlWithFeedback + { + /// + /// Delay between power off and power on for reboot + /// + int PowerCycleTimeMs { get; } + + /// + /// Reboot outlet + /// + void PowerCycle(); + } /// - /// Gets the BatteryPercentageFeedback + /// Interface for any device that contains a collection of IHasPowerReboot Devices /// - IntFeedback BatteryPercentageFeedback { get; } -} + public interface IHasControlledPowerOutlets : IKeyName + { + /// + /// Collection of IPduOutlets + /// + ReadOnlyDictionary PduOutlets { get; } -/// -/// Defines the contract for IHasBatteryCharging -/// -public interface IHasBatteryCharging : IHasBatteryStats -{ - /// - /// Gets the BatteryIsCharging - /// - BoolFeedback BatteryIsCharging { get; } -} + } -/// -/// Interface for any device that has multiple batteries that can be monitored -/// -public interface IHasBatteries : IKeyName -{ - /// - /// Collection of batteries - /// - ReadOnlyDictionary Batteries { get; } -} -/// -/// Defines the contract for IHasBatteryStatsExtended -/// -public interface IHasBatteryStatsExtended : IHasBatteryStats -{ - /// - /// Gets the InputVoltage in millivolts - /// - int InputVoltage { get; } - /// - /// Gets the OutputVoltage in millivolts - /// - int OutputVoltage { get; } - - /// - /// Gets the InputCurrent in milliamps - /// - int InptuCurrent { get; } - - /// - /// Gets the OutputCurrent in milliamps - /// - int OutputCurrent { get; } - - /// - /// Gets the InputVoltageFeedback - /// - IntFeedback InputVoltageFeedback { get; } - - /// - /// Gets the OutputVoltageFeedback - /// - IntFeedback OutputVoltageFeedback { get; } - - /// - /// Gets the InputCurrentFeedback - /// - IntFeedback InputCurrentFeedback { get; } - - /// - /// Gets the OutputCurrentFeedback - /// - IntFeedback OutputCurrentFeedback { get; } -} - -/// -/// Interface for any device that is able to control its power, has a configurable reboot time, and has batteries that can be monitored -/// -public interface IHasPowerCycleWithBattery : IHasPowerCycle, IHasBatteryStats -{ - -} - -/// -/// Interface for any device that is able to control it's power and has a configurable reboot time -/// -public interface IHasPowerCycle : IKeyName, IHasPowerControlWithFeedback -{ - - /// - /// Delay between power off and power on for reboot - /// - int PowerCycleTimeMs { get; } - - /// - /// Reboot outlet - /// - void PowerCycle(); -} - -/// -/// Interface for any device that contains a collection of IHasPowerReboot Devices -/// -public interface IHasControlledPowerOutlets : IKeyName -{ - /// - /// Collection of PDU outlets - /// - ReadOnlyDictionary PduOutlets { get; } - -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/PresentationDeviceType.cs b/src/PepperDash.Essentials.Core/Devices/PresentationDeviceType.cs index ed51738a..44474832 100644 --- a/src/PepperDash.Essentials.Core/Devices/PresentationDeviceType.cs +++ b/src/PepperDash.Essentials.Core/Devices/PresentationDeviceType.cs @@ -1,4 +1,13 @@ -namespace PepperDash.Essentials.Core +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.EthernetCommunication; +using Crestron.SimplSharpPro.UI; + +namespace PepperDash.Essentials.Core { /// /// Enumeration of PresentationSourceType values @@ -35,4 +44,4 @@ /// VCR } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/PresetListItem.cs b/src/PepperDash.Essentials.Core/Devices/PresetListItem.cs index eb9fd026..625dcec9 100644 --- a/src/PepperDash.Essentials.Core/Devices/PresetListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/PresetListItem.cs @@ -1,46 +1,51 @@ -using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core; -/// -/// Represents a PresetListItem -/// - -public class PresetListItem : AudioControlListItemBase +namespace PepperDash.Essentials.Core { /// - /// Gets the preset associated with this list item + /// Represents a PresetListItem /// - [JsonIgnore] - public IKeyName Preset - + public class PresetListItem : AudioControlListItemBase { - get + /// + /// Gets the preset associated with this list item + /// + [JsonIgnore] + public IKeyName Preset { - if (_preset == null) + get { - var parent = DeviceManager.GetDeviceForKey(ParentDeviceKey) as IDspPresets; - if (parent == null || !parent.Presets.ContainsKey(ItemKey)) - return null; - _preset = parent.Presets[ItemKey]; + if (_preset == null) + { + var parent = DeviceManager.GetDeviceForKey(ParentDeviceKey) as IDspPresets; + if (parent == null || !parent.Presets.ContainsKey(ItemKey)) + return null; + _preset = parent.Presets[ItemKey]; + } + return _preset; } - return _preset; } - } - private IKeyName _preset; + private IKeyName _preset; - /// - /// Gets the name from the device if it implements IKeyName or else returns the Name property - /// - [JsonProperty("preferredName")] - public string PreferredName - { - get + /// + /// Gets the name from the device if it implements IKeyName or else returns the Name property + /// + [JsonProperty("preferredName")] + public string PreferredName { - if (!string.IsNullOrEmpty(Name)) return Name; + get + { + if (!string.IsNullOrEmpty(Name)) return Name; - else return Preset.Name; + else return Preset.Name; + } } } } diff --git a/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs b/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs new file mode 100644 index 00000000..8549fd38 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Devices/ProcessorExtensionDeviceFactory.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; + +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a factory for creating processor extension devices. + /// + /// The type of the processor extension device. + [Obsolete("will be removed in a future version")] + public abstract class ProcessorExtensionDeviceFactory : IProcessorExtensionDeviceFactory where T : EssentialsDevice + { + #region IProcessorExtensionDeviceFactory Members + + /// + /// Gets or sets the TypeNames + /// + public List TypeNames { get; protected set; } + + /// + /// LoadFactories method + /// + public void LoadFactories() + { + foreach (var typeName in TypeNames) + { + string description = typeof(T).GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; + + ProcessorExtensionDeviceFactory.AddFactoryForType(typeName.ToLower(), description, typeof(T), BuildDevice); + } + } + + /// + /// The method that will build the device + /// + /// The device config + /// An instance of the device + public abstract EssentialsDevice BuildDevice(DeviceConfig dc); + + #endregion + + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/ReconfigurableDevice.cs b/src/PepperDash.Essentials.Core/Devices/ReconfigurableDevice.cs index a7dc2af2..ccb74a39 100644 --- a/src/PepperDash.Essentials.Core/Devices/ReconfigurableDevice.cs +++ b/src/PepperDash.Essentials.Core/Devices/ReconfigurableDevice.cs @@ -12,67 +12,95 @@ using PepperDash.Essentials.Core.Config; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace PepperDash.Essentials.Core.Devices; - -/// -/// -/// -public abstract class ReconfigurableDevice : EssentialsDevice, IReconfigurableDevice +namespace PepperDash.Essentials.Core.Devices { - public event EventHandler ConfigChanged; - - public DeviceConfig Config { get; private set; } - - protected ReconfigurableDevice(DeviceConfig config) - : base(config.Key) - { - SetNameHelper(config); - - Config = config; - } - /// - /// Sets the Config, calls CustomSetConfig and fires the ConfigChanged event + /// /// - /// - public void SetConfig(DeviceConfig config) + public abstract class ReconfigurableDevice : EssentialsDevice, IReconfigurableDevice { - Config = config; + /// + /// Event fired when the configuration changes + /// + public event EventHandler ConfigChanged; - SetNameHelper(config); + /// + /// Gets the current DeviceConfig + /// + public DeviceConfig Config { get; private set; } - CustomSetConfig(config); - - var handler = ConfigChanged; - if (handler != null) + /// + /// Constructor + /// + /// config of the device + protected ReconfigurableDevice(DeviceConfig config) + : base(config.Key) { - handler(this, new EventArgs()); + SetNameHelper(config); + + Config = config; + } + + /// + /// Sets the Config, calls CustomSetConfig and fires the ConfigChanged event + /// + /// + /// + /// SetConfig method + /// + public void SetConfig(DeviceConfig config) + { + Config = config; + + SetNameHelper(config); + + CustomSetConfig(config); + + var handler = ConfigChanged; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + + void SetNameHelper(DeviceConfig config) + { + if (!string.IsNullOrEmpty(config.Name)) + Name = config.Name; + } + + + + /// + /// Used by the extending class to allow for any custom actions to be taken (tell the ConfigWriter to write config, etc) + /// + /// config of the device + protected virtual void CustomSetConfig(DeviceConfig config) + { + ConfigWriter.UpdateDeviceConfig(config); } } - void SetNameHelper(DeviceConfig config) - { - if (!string.IsNullOrEmpty(config.Name)) - Name = config.Name; - } - - - /// - /// Used by the extending class to allow for any custom actions to be taken (tell the ConfigWriter to write config, etc) + /// A ReconfigurableDevice that is also bridgeable /// - /// - protected virtual void CustomSetConfig(DeviceConfig config) + public abstract class ReconfigurableBridgableDevice : ReconfigurableDevice, IBridgeAdvanced { - ConfigWriter.UpdateDeviceConfig(config); - } -} + /// + /// Constructor + /// + /// config of the device + protected ReconfigurableBridgableDevice(DeviceConfig config) : base(config) + { + } -public abstract class ReconfigurableBridgableDevice : ReconfigurableDevice, IBridgeAdvanced -{ - protected ReconfigurableBridgableDevice(DeviceConfig config) : base(config) - { + /// + /// LinkToApi method + /// + /// trilist to link + /// the join to start at + /// key to the join map + /// the bridge to use + public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); } - - public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/SmartObjectBaseTypes.cs b/src/PepperDash.Essentials.Core/Devices/SmartObjectBaseTypes.cs index 1b13203a..55e70de4 100644 --- a/src/PepperDash.Essentials.Core/Devices/SmartObjectBaseTypes.cs +++ b/src/PepperDash.Essentials.Core/Devices/SmartObjectBaseTypes.cs @@ -1,23 +1,24 @@  -namespace PepperDash.Essentials.Core; - -/// -/// Represents a SmartObjectJoinOffsets -/// -public class SmartObjectJoinOffsets +namespace PepperDash.Essentials.Core { - /// - /// Dpad Join Offset - /// - public const ushort Dpad = 1; + /// + /// Represents a SmartObjectJoinOffsets + /// + public class SmartObjectJoinOffsets + { + /// + /// Dpad Join Offset + /// + public const ushort Dpad = 1; - /// - /// Numpad Join Offset - /// - public const ushort Numpad = 2; + /// + /// Numpad Join Offset + /// + public const ushort Numpad = 2; - /// - /// PresetList Join Offset - /// - public const ushort PresetList = 6; + /// + /// PresetList Join Offset + /// + public const ushort PresetList = 6; + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs index 97ff9efe..deaeeae3 100644 --- a/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs +++ b/src/PepperDash.Essentials.Core/Devices/SourceListItem.cs @@ -4,252 +4,344 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the type of source list item, which can be a route, off, or other. -/// -public enum eSourceListItemType +namespace PepperDash.Essentials.Core { - /// - /// Represents a typical route. - /// - Route, - /// - /// Represents an off route. - /// - Off, - /// - /// Represents some other type of route - /// - Other, -} - -/// -/// Represents an item in a source list - can be deserialized into. -/// -public class SourceListItem -{ - /// - /// The key of the source item, which is used to identify it in the DeviceManager - /// - [JsonProperty("sourceKey")] - public string SourceKey { get; set; } - /// /// Defines the type of source list item, which can be a route, off, or other. /// This is used to categorize the source list items in a room. /// The type is serialized to JSON and can be used to determine how the item should be displayed or handled in the UI. - /// Returns the source Device for this, if it exists in DeviceManager /// - [JsonIgnore] - public IRoutingSource SourceDevice + public enum eSourceListItemType { - get - { - if (_SourceDevice == null) - _SourceDevice = DeviceManager.GetDeviceForKey(SourceKey); - return _SourceDevice; - } + /// + /// Represents a typical route. + /// + Route, + /// + /// Represents an off route. + /// + Off, + /// + /// Represents some other type of route + /// + Other, } - private IRoutingSource _SourceDevice; - /// - /// Gets either the source's Name or this AlternateName property, if - /// defined. If source doesn't exist, returns "Missing source" + /// Represents a SourceListItem /// - [JsonProperty("preferredName")] - public string PreferredName + public class SourceListItem { - get + /// + /// The key of the source item, which is used to identify it in the DeviceManager + /// + [JsonProperty("sourceKey")] + public string SourceKey { get; set; } + + /// + /// Returns the source Device for this, if it exists in DeviceManager + /// + [JsonIgnore] + public Device SourceDevice { - if (string.IsNullOrEmpty(Name)) + get { - if (SourceDevice == null) - return "---"; - return SourceDevice.Name; + if (_SourceDevice == null) + _SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device; + return _SourceDevice; } - return Name; + } + + private Device _SourceDevice; + + /// + /// Gets either the source's Name or this AlternateName property, if + /// defined. If source doesn't exist, returns "Missing source" + /// + [JsonProperty("preferredName")] + public string PreferredName + { + get + { + if (string.IsNullOrEmpty(Name)) + { + if (SourceDevice == null) + return "---"; + return SourceDevice.Name; + } + return Name; + } + } + + /// + /// A name that will override the source's name on the UI + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Specifies and icon for the source list item + /// + [JsonProperty("icon")] + public string Icon { get; set; } + + /// + /// Alternate icon + /// + [JsonProperty("altIcon")] + public string AltIcon { get; set; } + + /// + /// Indicates if the item should be included in the source list + /// + [JsonProperty("includeInSourceList")] + public bool IncludeInSourceList { get; set; } + + /// + /// Used to specify the order of the items in the source list when displayed + /// + [JsonProperty("order")] + public int Order { get; set; } + + /// + /// The key of the device for volume control + /// + [JsonProperty("volumeControlKey")] + public string VolumeControlKey { get; set; } + + /// + /// The type of source list item + /// + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] + public eSourceListItemType Type { get; set; } + + /// + /// The list of routes to execute for this source list item + /// + [JsonProperty("routeList")] + public List RouteList { get; set; } + + /// + /// Indicates if this source should be disabled for sharing to the far end call participants via codec content + /// + [JsonProperty("disableCodecSharing")] + public bool DisableCodecSharing { get; set; } + + /// + /// Indicates if this source should be disabled for routing to a shared output + /// + [JsonProperty("disableRoutedSharing")] + public bool DisableRoutedSharing { get; set; } + + /// + /// + /// + [JsonProperty("destinations")] + public List Destinations { get; set; } + /// + /// A means to reference a source list for this source item, in the event that this source has an input that can have sources routed to it + /// + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } + + /// + /// Indicates if the device associated with this source is controllable + /// + [JsonProperty("isControllable")] + public bool IsControllable { get; set; } + + /// + /// Indicates that the device associated with this source has audio available + /// + [JsonProperty("isAudioSource")] + public bool IsAudioSource { get; set; } + + /// + /// Hide source on UI when Avanced Sharing is enabled + /// + [JsonProperty("disableAdvancedRouting")] + public bool DisableAdvancedRouting { get; set; } + + /// + /// Hide source on UI when Simpl Sharing is enabled + /// + [JsonProperty("disableSimpleRouting")] + public bool DisableSimpleRouting { get; set; } + + /// + /// The key of the device that provides video sync for this source item + /// + [JsonProperty("syncProviderDeviceKey")] + public string SyncProviderDeviceKey { get; set; } + + /// + /// Indicates if the source supports USB connections + /// + [JsonProperty("supportsUsb")] + public bool SupportsUsb { get; set; } + + /// + /// The key of the source port associated with this source item + /// This is used to identify the specific port on the source device that this item refers to for advanced routing + /// + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + + + /// + /// Default constructor for SourceListItem, initializes the Icon to "Blank" + /// + public SourceListItem() + { + Icon = "Blank"; + } + + /// + /// Returns a string representation of the SourceListItem, including the SourceKey and Name + /// + /// A string representation of the SourceListItem + public override string ToString() + { + return $"{SourceKey}:{Name}"; } } /// - /// A name that will override the source's name on the UI + /// Represents a route in a source list item, which defines the source and destination keys and the type of signal being routed /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// Specifies and icon for the source list item - /// - [JsonProperty("icon")] - public string Icon { get; set; } - - /// - /// Alternate icon - /// - [JsonProperty("altIcon")] - public string AltIcon { get; set; } - - /// - /// Indicates if the item should be included in the source list - /// - [JsonProperty("includeInSourceList")] - public bool IncludeInSourceList { get; set; } - - /// - /// Used to specify the order of the items in the source list when displayed - /// - [JsonProperty("order")] - public int Order { get; set; } - - /// - /// The key of the device for volume control - /// - [JsonProperty("volumeControlKey")] - public string VolumeControlKey { get; set; } - - /// - /// The type of source list item - /// - [JsonProperty("type")] - [JsonConverter(typeof(StringEnumConverter))] - public eSourceListItemType Type { get; set; } - - /// - /// The list of routes to execute for this source list item - /// - [JsonProperty("routeList")] - public List RouteList { get; set; } - - /// - /// Indicates if this source should be disabled for sharing to the far end call participants via codec content - /// - [JsonProperty("disableCodecSharing")] - public bool DisableCodecSharing { get; set; } - - /// - /// Indicates if this source should be disabled for routing to a shared output - /// - [JsonProperty("disableRoutedSharing")] - public bool DisableRoutedSharing { get; set; } - - /// - /// A means to reference a source list for this source item, in the event that this source has an input that can have sources routed to it - /// - [JsonProperty("sourceListKey")] - public string SourceListKey { get; set; } - - /// - /// Indicates if the device associated with this source is controllable - /// - [JsonProperty("isControllable")] - public bool IsControllable { get; set; } - - /// - /// Indicates that the device associated with this source has audio available - /// - [JsonProperty("isAudioSource")] - public bool IsAudioSource { get; set; } - - /// - /// Hide source on UI when Avanced Sharing is enabled - /// - [JsonProperty("disableAdvancedRouting")] - public bool DisableAdvancedRouting { get; set; } - - /// - /// Hide source on UI when Simpl Sharing is enabled - /// - [JsonProperty("disableSimpleRouting")] - public bool DisableSimpleRouting { get; set; } - - /// - /// The key of the device that provides video sync for this source item - /// - [JsonProperty("syncProviderDeviceKey")] - public string SyncProviderDeviceKey { get; set; } - - /// - /// Indicates if the source supports USB connections - /// - [JsonProperty("supportsUsb")] - public bool SupportsUsb { get; set; } - - /// - /// The key of the source port associated with this source item - /// This is used to identify the specific port on the source device that this item refers to for advanced routing - /// - [JsonProperty("sourcePortKey")] - public string SourcePortKey { get; set; } - - - /// - /// Default constructor for SourceListItem, initializes the Icon to "Blank" - /// - public SourceListItem() + public class SourceRouteListItem { - Icon = "Blank"; + /// + /// The key of the source device to route from + /// + [JsonProperty("sourceKey")] + public string SourceKey { get; set; } + + /// + /// The key of the source port to route from + /// + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } + + /// + /// The key of the destination device to route to + /// + [JsonProperty("destinationKey")] + public string DestinationKey { get; set; } + + /// + /// The key of the destination port to route to + /// + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } + + /// + /// The type of signal being routed, such as audio or video + /// + [JsonProperty("type")] + public eRoutingSignalType Type { get; set; } + + /// + /// Key for a destination list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined, + /// then the direct route method should be used. + /// + [JsonProperty("destinationListItemKey", NullValueHandling = NullValueHandling.Ignore)] + public string DestinationListItemKey { get; set; } + + /// + /// Key for a source list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined, + /// then the direct route method should be used. + /// + [JsonProperty("sourceListItemKey", NullValueHandling = NullValueHandling.Ignore)] + public string SourceListItemKey { get; set; } } /// - /// Returns a string representation of the SourceListItem, including the SourceKey and Name + /// Defines the valid destination types for SourceListItems in a room /// - /// A string representation of the SourceListItem - public override string ToString() + [Obsolete] + public enum eSourceListItemDestinationTypes { - return $"{SourceKey}:{Name}"; + /// + /// Default display, used for the main video output in a room + /// + defaultDisplay, + /// + /// Left display + /// + leftDisplay, + /// + /// Right display + /// + rightDisplay, + /// + /// Center display + /// + centerDisplay, + /// + /// Program audio, used for the main audio output in a room + /// + programAudio, + /// + /// Codec content, used for sharing content to the far end in a video call + /// + codecContent, + /// + /// Front left display, used for rooms with multiple displays + /// + frontLeftDisplay, + /// + /// Front right display, used for rooms with multiple displays + /// + frontRightDisplay, + /// + /// Rear left display, used for rooms with multiple displays + /// + rearLeftDisplay, + /// + /// Rear right display, used for rooms with multiple displays + /// + rearRightDisplay, + /// + /// Auxiliary display 1, used for additional displays in a room + /// + auxDisplay1, + /// + /// Auxiliary display 2, used for additional displays in a room + /// + auxDisplay2, + /// + /// Auxiliary display 3, used for additional displays in a room + /// + auxDisplay3, + /// + /// Auxiliary display 4, used for additional displays in a room + /// + auxDisplay4, + /// + /// Auxiliary display 5, used for additional displays in a room + /// + auxDisplay5, + /// + /// Auxiliary display 6, used for additional displays in a room + /// + auxDisplay6, + /// + /// Auxiliary display 7, used for additional displays in a room + /// + auxDisplay7, + /// + /// Auxiliary display 8, used for additional displays in a room + /// + auxDisplay8, + /// + /// Auxiliary display 9, used for additional displays in a room + /// + auxDisplay9, + /// + /// Auxiliary display 10, used for additional displays in a room + /// + auxDisplay10, } -} - -/// -/// Represents a route in a source list item, which defines the source and destination keys and the type of signal being routed -/// -public class SourceRouteListItem -{ - /// - /// The key of the source device to route from - /// - [JsonProperty("sourceKey")] - public string SourceKey { get; set; } - - /// - /// The key of the source port to route from - /// - [JsonProperty("sourcePortKey")] - public string SourcePortKey { get; set; } - - /// - /// The key of the destination device to route to - /// - [JsonProperty("destinationKey")] - public string DestinationKey { get; set; } - - /// - /// The key of the destination port to route to - /// - [JsonProperty("destinationPortKey")] - public string DestinationPortKey { get; set; } - - /// - /// The type of signal being routed, such as audio or video - /// - [JsonProperty("type")] - public eRoutingSignalType Type { get; set; } - - /// - /// Key for a destination list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined, - /// then the direct route method should be used. - /// - [JsonProperty("destinationListItemKey", NullValueHandling = NullValueHandling.Ignore)] - public string DestinationListItemKey { get; set; } - - /// - /// Key for a source list item. If BOTH SourceListItemKey AND DestinationListItemKey are defined, - /// then the direct route method should be used. - /// - [JsonProperty("sourceListItemKey", NullValueHandling = NullValueHandling.Ignore)] - public string SourceListItemKey { get; set; } -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Devices/VolumeDeviceChangeEventArgs.cs b/src/PepperDash.Essentials.Core/Devices/VolumeDeviceChangeEventArgs.cs index 7aeac9ee..ae2895db 100644 --- a/src/PepperDash.Essentials.Core/Devices/VolumeDeviceChangeEventArgs.cs +++ b/src/PepperDash.Essentials.Core/Devices/VolumeDeviceChangeEventArgs.cs @@ -1,7 +1,14 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; +using PepperDash.Core; +using PepperDash.Essentials.Core; +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -51,3 +58,4 @@ namespace PepperDash.Essentials.Core; /// DidChange } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Ethernet/EthernetStatistics.cs b/src/PepperDash.Essentials.Core/Ethernet/EthernetStatistics.cs index c5097d07..0f5c2863 100644 --- a/src/PepperDash.Essentials.Core/Ethernet/EthernetStatistics.cs +++ b/src/PepperDash.Essentials.Core/Ethernet/EthernetStatistics.cs @@ -6,51 +6,52 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.Ethernet; - -/// -/// Ethernet settings feedbacks -/// -public static class EthernetSettings +namespace PepperDash.Essentials.Core.Ethernet { /// - /// Link active feedback + /// Ethernet settings feedbacks /// - public static readonly BoolFeedback LinkActive = new BoolFeedback("LinkActive", - () => true); + public static class EthernetSettings + { + /// + /// Link active feedback + /// + public static readonly BoolFeedback LinkActive = new BoolFeedback("LinkActive", + () => true); - /// - /// DHCP active feedback - /// - public static readonly BoolFeedback DhcpActive = new BoolFeedback("DhcpActive", - () => CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, 0) == "ON"); + /// + /// DHCP active feedback + /// + public static readonly BoolFeedback DhcpActive = new BoolFeedback("DhcpActive", + () => CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, 0) == "ON"); - /// - /// Hostname feedback - /// - public static readonly StringFeedback Hostname = new StringFeedback("Hostname", - () => CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0)); + /// + /// Hostname feedback + /// + public static readonly StringFeedback Hostname = new StringFeedback("Hostname", + () => CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0)); - /// - /// IP Address feedback - /// - public static readonly StringFeedback IpAddress0 = new StringFeedback("IpAddress0", - () => CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + /// + /// IP Address feedback + /// + public static readonly StringFeedback IpAddress0 = new StringFeedback("IpAddress0", + () => CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); - /// - /// Subnet Mask feedback - /// - public static readonly StringFeedback SubnetMask0 = new StringFeedback("SubnetMask0", - () => CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, 0)); + /// + /// Subnet Mask feedback + /// + public static readonly StringFeedback SubnetMask0 = new StringFeedback("SubnetMask0", + () => CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, 0)); - /// - /// Default Gateway feedback - /// - public static readonly StringFeedback DefaultGateway0 = new StringFeedback("DefaultGateway0", - () => CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0)); + /// + /// Default Gateway feedback + /// + public static readonly StringFeedback DefaultGateway0 = new StringFeedback("DefaultGateway0", + () => CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0)); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Extensions/IpAddressExtensions.cs b/src/PepperDash.Essentials.Core/Extensions/IpAddressExtensions.cs index 6da2211a..35ec1b8a 100644 --- a/src/PepperDash.Essentials.Core/Extensions/IpAddressExtensions.cs +++ b/src/PepperDash.Essentials.Core/Extensions/IpAddressExtensions.cs @@ -5,81 +5,82 @@ using System.Net; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core; - -/// -/// Extensions for IPAddress to provide additional functionality such as getting broadcast address, network address, and checking if two addresses are in the same subnet. -/// -public static class IPAddressExtensions +namespace PepperDash.Essentials.Core { /// - /// Get the broadcast address for a given IP address and subnet mask. + /// Extensions for IPAddress to provide additional functionality such as getting broadcast address, network address, and checking if two addresses are in the same subnet. /// - /// Address to check - /// Subnet mask in a.b.c.d format - /// Broadcast address - /// - /// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the broadcast address will be 192.168.1.255 - /// - /// - public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask) + public static class IPAddressExtensions { - byte[] ipAdressBytes = address.GetAddressBytes(); - byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); - - if (ipAdressBytes.Length != subnetMaskBytes.Length) - throw new ArgumentException("Lengths of IP address and subnet mask do not match."); - - byte[] broadcastAddress = new byte[ipAdressBytes.Length]; - for (int i = 0; i < broadcastAddress.Length; i++) + /// + /// Get the broadcast address for a given IP address and subnet mask. + /// + /// Address to check + /// Subnet mask in a.b.c.d format + /// Broadcast address + /// + /// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the broadcast address will be 192.168.1.255 + /// + /// + public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask) { - broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255)); + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255)); + } + return new IPAddress(broadcastAddress); } - return new IPAddress(broadcastAddress); - } - /// - /// Get the network address for a given IP address and subnet mask. - /// - /// Address to check - /// Subnet mask in a.b.c.d - /// Network Address - /// /// - /// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the network address will be 192.168.1.0 - /// - /// - public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask) - { - byte[] ipAdressBytes = address.GetAddressBytes(); - byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); - - if (ipAdressBytes.Length != subnetMaskBytes.Length) - throw new ArgumentException("Lengths of IP address and subnet mask do not match."); - - byte[] broadcastAddress = new byte[ipAdressBytes.Length]; - for (int i = 0; i < broadcastAddress.Length; i++) + /// + /// Get the network address for a given IP address and subnet mask. + /// + /// Address to check + /// Subnet mask in a.b.c.d + /// Network Address + /// /// + /// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the network address will be 192.168.1.0 + /// + /// + public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask) { - broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + } + return new IPAddress(broadcastAddress); } - return new IPAddress(broadcastAddress); - } - /// - /// Determine if two IP addresses are in the same subnet. - /// - /// Address to check - /// Second address to check - /// Subnet mask to use to compare the 2 IP Address - /// True if addresses are in the same subnet - /// - /// If the input IP addresses are 192.168.1.100 and 192.168.1.200, and the subnet mask is 255.255.255.0, this will return true. - /// If the input IP addresses are 10.1.1.100 and 192.168.1.100, and the subnet mask is 255.255.255.0, this will return false. - /// - public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask) - { - IPAddress network1 = address.GetNetworkAddress(subnetMask); - IPAddress network2 = address2.GetNetworkAddress(subnetMask); + /// + /// Determine if two IP addresses are in the same subnet. + /// + /// Address to check + /// Second address to check + /// Subnet mask to use to compare the 2 IP Address + /// True if addresses are in the same subnet + /// + /// If the input IP addresses are 192.168.1.100 and 192.168.1.200, and the subnet mask is 255.255.255.0, this will return true. + /// If the input IP addresses are 10.1.1.100 and 192.168.1.100, and the subnet mask is 255.255.255.0, this will return false. + /// + public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask) + { + IPAddress network1 = address.GetNetworkAddress(subnetMask); + IPAddress network2 = address2.GetNetworkAddress(subnetMask); - return network1.Equals(network2); + return network1.Equals(network2); + } } } diff --git a/src/PepperDash.Essentials.Core/Extensions/JsonExtensions.cs b/src/PepperDash.Essentials.Core/Extensions/JsonExtensions.cs index 1a6bbb6b..74697525 100644 --- a/src/PepperDash.Essentials.Core/Extensions/JsonExtensions.cs +++ b/src/PepperDash.Essentials.Core/Extensions/JsonExtensions.cs @@ -1,44 +1,52 @@ -using System.Collections.Generic; + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace PepperDash.Essentials.Core; - -/// -/// JsonExtensions class -/// -public static class JsonExtensions +namespace PepperDash.Essentials.Core { /// - /// FindTokens method + /// JsonExtensions class /// - /// token for the container - /// name of the token to find - /// list of matching tokens - public static List FindTokens(this JToken containerToken, string name) + public static class JsonExtensions { - List matches = new List(); - FindTokens(containerToken, name, matches); - return matches; - } - - private static void FindTokens(JToken containerToken, string name, List matches) - { - if (containerToken.Type == JTokenType.Object) + /// + /// FindTokens method + /// + /// token for the container + /// name of the token to find + /// list of matching tokens + public static List FindTokens(this JToken containerToken, string name) { - foreach (JProperty child in containerToken.Children()) - { - if (child.Name == name) - { - matches.Add(child.Value); - } - FindTokens(child.Value, name, matches); - } + List matches = new List(); + FindTokens(containerToken, name, matches); + return matches; } - else if (containerToken.Type == JTokenType.Array) + + private static void FindTokens(JToken containerToken, string name, List matches) { - foreach (JToken child in containerToken.Children()) + if (containerToken.Type == JTokenType.Object) { - FindTokens(child, name, matches); + foreach (JProperty child in containerToken.Children()) + { + if (child.Name == name) + { + matches.Add(child.Value); + } + FindTokens(child.Value, name, matches); + } + } + else if (containerToken.Type == JTokenType.Array) + { + foreach (JToken child in containerToken.Children()) + { + FindTokens(child, name, matches); + } } } } diff --git a/src/PepperDash.Essentials.Core/Extensions/StringExtensions.cs b/src/PepperDash.Essentials.Core/Extensions/StringExtensions.cs index bdbe69ad..9dbb6244 100644 --- a/src/PepperDash.Essentials.Core/Extensions/StringExtensions.cs +++ b/src/PepperDash.Essentials.Core/Extensions/StringExtensions.cs @@ -5,66 +5,77 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// StringExtensions class -/// -public static class StringExtensions +namespace PepperDash.Essentials.Core { /// - /// Returns null if a string is empty or made of only whitespace characters, otherwise returns the string + /// StringExtensions class /// - /// string input - /// null if the string is wempty or made of only whitespace characters, otherwise returns the string - public static string NullIfWhiteSpace(this string s) + public static class StringExtensions { - return string.IsNullOrEmpty(s.Trim()) ? null : s; - } + /// + /// Returns null if a string is empty, otherwise returns the string + /// + /// string input + /// null if the string is emtpy, otherwise returns the string + public static string NullIfEmpty(this string s) + { + return string.IsNullOrEmpty(s) ? null : s; + } - /// - /// Returns a replacement string if the input string is empty or made of only whitespace characters, otherwise returns the input string - /// - /// input string - /// string to replace with if input string is empty or whitespace - /// returns newString if s is null, emtpy, or made of whitespace characters, otherwise returns s - public static string ReplaceIfNullOrEmpty(this string s, string newString) - { - return string.IsNullOrEmpty(s) ? newString : s; - } + /// + /// Returns null if a string is empty or made of only whitespace characters, otherwise returns the string + /// + /// string input + /// null if the string is wempty or made of only whitespace characters, otherwise returns the string + public static string NullIfWhiteSpace(this string s) + { + return string.IsNullOrEmpty(s.Trim()) ? null : s; + } - /// - /// Overload for Contains that allows setting an explicit String Comparison - /// - /// Source String - /// String to check in Source String - /// Comparison parameters - /// true of string contains "toCheck" - public static bool Contains(this string source, string toCheck, StringComparison comp) - { - if (string.IsNullOrEmpty(source)) return false; - return source.IndexOf(toCheck, comp) >= 0; - } + /// + /// Returns a replacement string if the input string is empty or made of only whitespace characters, otherwise returns the input string + /// + /// input string + /// string to replace with if input string is empty or whitespace + /// returns newString if s is null, emtpy, or made of whitespace characters, otherwise returns s + public static string ReplaceIfNullOrEmpty(this string s, string newString) + { + return string.IsNullOrEmpty(s) ? newString : s; + } - /// - /// Performs TrimStart() and TrimEnd() on source string - /// - /// String to Trim - /// Trimmed String - public static string TrimAll(this string source) - { - return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart().TrimEnd(); - } + /// + /// Overload for Contains that allows setting an explicit String Comparison + /// + /// Source String + /// String to check in Source String + /// Comparison parameters + /// true of string contains "toCheck" + public static bool Contains(this string source, string toCheck, StringComparison comp) + { + if (string.IsNullOrEmpty(source)) return false; + return source.IndexOf(toCheck, comp) >= 0; + } - /// - /// Performs TrimStart(chars char[]) and TrimEnd(chars char[]) on source string. - /// - /// String to Trim - /// Char Array to trim from string - /// Trimmed String - public static string TrimAll(this string source, char[] chars) - { - return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart(chars).TrimEnd(chars); - } + /// + /// Performs TrimStart() and TrimEnd() on source string + /// + /// String to Trim + /// Trimmed String + public static string TrimAll(this string source) + { + return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart().TrimEnd(); + } + /// + /// Performs TrimStart(chars char[]) and TrimEnd(chars char[]) on source string. + /// + /// String to Trim + /// Char Array to trim from string + /// Trimmed String + public static string TrimAll(this string source, char[] chars) + { + return string.IsNullOrEmpty(source) ? string.Empty : source.TrimStart(chars).TrimEnd(chars); + } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs index 74796da1..c79be592 100644 --- a/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/DeviceFactory.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Reflection; @@ -12,259 +11,272 @@ using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Provides functionality for managing and registering device factories, including loading plugin-based factories and -/// retrieving devices based on their configuration. -/// -/// The class is responsible for discovering and registering device factories -/// from plugins, as well as providing methods to retrieve devices based on their configuration. It maintains a -/// collection of factory methods that are keyed by device type names, allowing for extensibility through plugins. This -/// class also handles metadata retrieval and secret management for device configurations. -public class DeviceFactory +namespace PepperDash.Essentials.Core { /// - /// Initializes a new instance of the class and loads all available device factories - /// from the current assembly. + /// Provides functionality for managing and registering device factories, including loading plugin-based factories and + /// retrieving devices based on their configuration. /// - /// This constructor scans the executing assembly for types that implement the interface and are not abstract or interfaces. For each valid type, an instance is - /// created and passed to the LoadDeviceFactories method for further processing. If a type cannot be - /// instantiated, an informational log message is generated, and the process continues with the remaining - /// types. - public DeviceFactory() + /// The class is responsible for discovering and registering device factories + /// from plugins, as well as providing methods to retrieve devices based on their configuration. It maintains a + /// collection of factory methods that are keyed by device type names, allowing for extensibility through plugins. This + /// class also handles metadata retrieval and secret management for device configurations. + public class DeviceFactory { - var programAssemblies = Directory.GetFiles(InitialParametersClass.ProgramDirectory.ToString(), "*.dll"); - - foreach (var assembly in programAssemblies) + /// + /// Initializes a new instance of the class and loads all available device factories + /// from the current assembly. + /// + /// This constructor scans the executing assembly for types that implement the interface and are not abstract or interfaces. For each valid type, an instance is + /// created and passed to the LoadDeviceFactories method for further processing. If a type cannot be + /// instantiated, an informational log message is generated, and the process continues with the remaining + /// types. + public DeviceFactory() { - try + var programAssemblies = Directory.GetFiles(InitialParametersClass.ProgramDirectory.ToString(), "*.dll"); + + // Assemblies known to cause load errors that should be skipped + var assembliesToSkip = new[] { "CrestronOnvif.dll" }; + + foreach (var assembly in programAssemblies) { - Assembly.LoadFrom(assembly); - } - catch (Exception e) - { - Debug.LogError("Unable to load assembly: {assemblyName} - {message}", assembly, e.Message); - } - } + if (assembliesToSkip.Any(a => Path.GetFileName(assembly).Equals(a, StringComparison.OrdinalIgnoreCase))) + { + Debug.LogMessage(LogEventLevel.Verbose, "Skipping assembly: {assemblyName}", Path.GetFileName(assembly)); + continue; + } - var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - - // Loop through all loaded assemblies that contain at least 1 type that implements IDeviceFactory - foreach (var assembly in loadedAssemblies) - { - Debug.LogDebug("loaded assembly: {assemblyName}", assembly.GetName().Name); - - PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly); - - var types = assembly.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types == null || !types.Any()) - { - Debug.LogDebug("No DeviceFactory types found in assembly: {assemblyName}", assembly.GetName().Name); - continue; - } - - foreach (var type in types) - { try { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - LoadDeviceFactories(factory); + Assembly.LoadFrom(assembly); } catch (Exception e) { - Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, type.Name); + Debug.LogError("Unable to load assembly: {assemblyName} - {message}", assembly, e.Message); } } - } - } + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - /// - /// Loads device factories from the specified plugin device factory and registers them for use. - /// - /// This method retrieves metadata from the provided , including - /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type - /// names are converted to lowercase for registration. - /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. - private static void LoadDeviceFactories(IDeviceFactory deviceFactory) - { - foreach (var typeName in deviceFactory.TypeNames) - { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); - } - } - - /// - /// A dictionary of factory methods, keyed by config types, added by plugins. - /// These methods are looked up and called by GetDevice in this class. - /// - private static readonly Dictionary FactoryMethods = - new(StringComparer.OrdinalIgnoreCase); - - /// - /// Registers a factory method for creating instances of a specific type. - /// - /// This method associates a type name with a factory method, allowing instances of the type to - /// be created dynamically. The factory method is stored internally and can be retrieved or invoked as - /// needed. - /// The name of the type for which the factory method is being registered. This value cannot be null or empty. - /// A delegate that defines the factory method. The delegate takes a parameter and - /// returns an instance of . - public static void AddFactoryForType(string typeName, Func method) - { - FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method }); - } - - /// - /// Registers a factory method for creating instances of a specific device type. - /// - /// If a factory method for the specified already exists, the method - /// will not overwrite it and will log an informational message instead. - /// The unique name of the device type. This serves as the key for identifying the factory method. - /// A brief description of the device type. This is used for informational purposes. - /// The of the device being registered. This represents the runtime type of the device. - /// A factory method that takes a as input and returns an instance of . - public static void AddFactoryForType(string typeName, string description, Type Type, Func method) - { - if (FactoryMethods.ContainsKey(typeName)) - { - Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName); - return; - } - - var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; - - FactoryMethods.Add(typeName, wrapper); - } - - private static void CheckForSecrets(IEnumerable obj) - { - foreach (var prop in obj.Where(prop => prop.Value as JObject != null)) - { - if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase)) + // Loop through all loaded assemblies that contain at least 1 type that implements IDeviceFactory + foreach (var assembly in loadedAssemblies) { - var secret = GetSecret(prop.Children().First().ToObject()); + Debug.LogDebug("loaded assembly: {assemblyName}", assembly.GetName()?.Name ?? "Unknown"); + + PluginLoader.AddLoadedAssembly(assembly.GetName()?.Name ?? "Unknown", assembly); + + var types = assembly.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (types == null || !types.Any()) + { + Debug.LogDebug("No DeviceFactory types found in assembly: {assemblyName}", assembly.GetName().Name); + continue; + } + + foreach (var type in types) + { + try + { + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogError("Unable to load type: '{message}' DeviceFactory: {type}", e.Message, type.Name); + } + } - prop.Parent.Replace(secret); } - if (prop.Value is not JObject recurseProp) return; - CheckForSecrets(recurseProp.Properties()); } - } - private static string GetSecret(SecretsPropertiesConfig data) - { - var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); - if (secretProvider == null) return null; - var secret = secretProvider.GetSecret(data.Key); - if (secret != null) return (string)secret.Value; - Debug.LogMessage(LogEventLevel.Debug, - "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", - data.Provider, data.Key); - return string.Empty; - } - - - /// - /// Creates and returns a device instance based on the provided . - /// - /// This method attempts to create a device using the type specified in the - /// parameter. If the type corresponds to a registered factory method, the device is created and returned. If the - /// type is unrecognized or an exception occurs, the method logs the error and returns . - /// The configuration object containing the key, name, type, and properties required to create the device. - /// An instance of a device that implements , or if the device type is - /// not recognized or an error occurs during creation. - public static IKeyed GetDevice(DeviceConfig dc) - { - try + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) { - var localDc = new DeviceConfig(dc); - - var key = localDc.Key; - var name = localDc.Name; - var type = localDc.Type; - var properties = localDc.Properties; - - var typeName = localDc.Type.ToLower(); - - if (properties is JObject jObject) + foreach (var typeName in deviceFactory.TypeNames) { - var jProp = jObject.Properties(); + string description = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; - CheckForSecrets(jProp); + AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } + } + + /// + /// A dictionary of factory methods, keyed by config types, added by plugins. + /// These methods are looked up and called by GetDevice in this class. + /// + private static readonly Dictionary FactoryMethods = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Registers a factory method for creating instances of a specific type. + /// + /// This method associates a type name with a factory method, allowing instances of the type to + /// be created dynamically. The factory method is stored internally and can be retrieved or invoked as + /// needed. + /// The name of the type for which the factory method is being registered. This value cannot be null or empty. + /// A delegate that defines the factory method. The delegate takes a parameter and + /// returns an instance of . + public static void AddFactoryForType(string typeName, Func method) + { + FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method }); + } + + /// + /// Registers a factory method for creating instances of a specific device type. + /// + /// If a factory method for the specified already exists, the method + /// will not overwrite it and will log an informational message instead. + /// The unique name of the device type. This serves as the key for identifying the factory method. + /// A brief description of the device type. This is used for informational purposes. + /// The of the device being registered. This represents the runtime type of the device. + /// A factory method that takes a as input and returns an instance of . + public static void AddFactoryForType(string typeName, string description, Type Type, Func method) + { + if (FactoryMethods.ContainsKey(typeName)) + { + Debug.LogInformation("Unable to add type: '{typeName}'. Already exists in DeviceFactory", typeName); + return; } - if (!FactoryMethods.TryGetValue(typeName, out var wrapper)) + var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; + + FactoryMethods.Add(typeName, wrapper); + } + + private static void CheckForSecrets(IEnumerable obj) + { + foreach (var prop in obj.Where(prop => prop.Value as JObject != null)) { - Debug.LogWarning("Device type '{typeName}' not found in DeviceFactory", typeName); + if (prop.Name.Equals("secret", StringComparison.CurrentCultureIgnoreCase)) + { + var secret = GetSecret(prop.Children().First().ToObject()); + + prop.Parent.Replace(secret); + } + + if (!(prop.Value is JObject recurseProp)) continue; + + CheckForSecrets(recurseProp.Properties()); + } + } + + private static string GetSecret(SecretsPropertiesConfig data) + { + var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); + if (secretProvider == null) return null; + var secret = secretProvider.GetSecret(data.Key); + if (secret != null) return (string)secret.Value; + Debug.LogMessage(LogEventLevel.Debug, + "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", + data.Provider, data.Key); + return string.Empty; + } + + + /// + /// Creates and returns a device instance based on the provided . + /// + /// This method attempts to create a device using the type specified in the + /// parameter. If the type corresponds to a registered factory method, the device is created and returned. If the + /// type is unrecognized or an exception occurs, the method logs the error and returns . + /// The configuration object containing the key, name, type, and properties required to create the device. + /// An instance of a device that implements , or if the device type is + /// not recognized or an error occurs during creation. + public static IKeyed GetDevice(DeviceConfig dc) + { + try + { + var localDc = new DeviceConfig(dc); + + var key = localDc.Key; + var name = localDc.Name; + var type = localDc.Type; + var properties = localDc.Properties; + + var typeName = localDc.Type.ToLower(); + + if (properties is JObject jObject) + { + var jProp = jObject.Properties(); + + CheckForSecrets(jProp); + } + + if (!FactoryMethods.TryGetValue(typeName, out var wrapper)) + { + Debug.LogWarning("Device type '{typeName}' not found in DeviceFactory", typeName); + return null; + } + + Debug.LogInformation("Loading '{type}' from {assemblyName}", typeName, wrapper.Type.Assembly.FullName); + + // Check for types that have been added by plugin dlls. + return wrapper.FactoryMethod(localDc); + } + catch (Exception ex) + { + Debug.LogError(ex, "Exception occurred while creating device {key}: {message}", dc.Key, ex.Message); + Debug.LogDebug(ex, "Exception details: {stackTrace}", ex.StackTrace); return null; } - - Debug.LogInformation("Loading '{type}' from {assemblyName}", typeName, wrapper.Type.Assembly.FullName); - - // Check for types that have been added by plugin dlls. - return wrapper.FactoryMethod(localDc); } - catch (Exception ex) + + /// + /// Displays a list of device factory types that match the specified filter. + /// + /// The method outputs the filtered list of device factory types to the console, including their + /// key, type, and description. If a type is not specified by the plugin, it will be displayed as "Not Specified by + /// Plugin." + /// A string used to filter the device factory types by their keys. If the filter is null or empty, all device + /// factory types are displayed. + public static void GetDeviceFactoryTypes(string filter) { - Debug.LogError(ex, "Exception occurred while creating device {deviceKey}: {message}", dc.Key, ex.Message); - return null; - } - } + var types = !string.IsNullOrEmpty(filter) + ? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value) + : FactoryMethods; - /// - /// Displays a list of device factory types that match the specified filter. - /// - /// The method outputs the filtered list of device factory types to the console, including their - /// key, type, and description. If a type is not specified by the plugin, it will be displayed as "Not Specified by - /// Plugin." - /// A string used to filter the device factory types by their keys. If the filter is null or empty, all device - /// factory types are displayed. - public static void GetDeviceFactoryTypes(string filter) - { - var types = !string.IsNullOrEmpty(filter) - ? FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value) - : FactoryMethods; + CrestronConsole.ConsoleCommandResponse("Device Types:"); - CrestronConsole.ConsoleCommandResponse("Device Types:"); - - foreach (var type in types.OrderBy(t => t.Key)) - { - var description = type.Value.Description; - var Type = "Not Specified by Plugin"; - - if (type.Value.Type != null) + foreach (var type in types.OrderBy(t => t.Key)) { - Type = type.Value.Type.FullName; + var description = type.Value.Description; + var Type = "Not Specified by Plugin"; + + if (type.Value.Type != null) + { + Type = type.Value.Type.FullName; + } + + CrestronConsole.ConsoleCommandResponse( + "Type: '{0}'\r\n" + + " Type: '{1}'\r\n" + + " Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine); } - - CrestronConsole.ConsoleCommandResponse( - "Type: '{0}'\r\n" + - " Type: '{1}'\r\n" + - " Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine); } - } - /// - /// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string. - /// - /// A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be - /// included. If or empty, all entries are returned. - /// A dictionary where the keys are strings representing device identifiers and the values are instances. The dictionary may be empty if no entries match the filter. - public static Dictionary GetDeviceFactoryDictionary(string filter) - { - return string.IsNullOrEmpty(filter) - ? FactoryMethods - : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); + /// + /// Retrieves a dictionary of device factory wrappers, optionally filtered by a specified string. + /// + /// A string used to filter the dictionary keys. Only entries with keys containing the specified filter will be + /// included. If or empty, all entries are returned. + /// A dictionary where the keys are strings representing device identifiers and the values are instances. The dictionary may be empty if no entries match the filter. + public static Dictionary GetDeviceFactoryDictionary(string filter) + { + return string.IsNullOrEmpty(filter) + ? FactoryMethods + : FactoryMethods.Where(k => k.Key.Contains(filter)).ToDictionary(k => k.Key, k => k.Value); + } } } diff --git a/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs index 6672532f..b267edf6 100644 --- a/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/IDeviceFactory.cs @@ -1,29 +1,30 @@ -using PepperDash.Essentials.Core.Config; -using System; +using System; using System.Collections.Generic; +using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials.Core; - -/// -/// Defines a class that is capable of loading device types -/// -public interface IDeviceFactory +namespace PepperDash.Essentials.Core { /// - /// Gets the type of the factory associated with the current instance. + /// Defines the contract for IDeviceFactory /// - Type FactoryType { get; } + public interface IDeviceFactory + { + /// + /// Gets the type of the factory associated with the current instance. + /// + Type FactoryType { get; } - /// - /// Gets a list of type names associated with the current plugin. - /// - List TypeNames { get; } + /// + /// Gets a list of type names associated with the current plugin. + /// + List TypeNames { get; } - /// - /// Builds and returns an instance based on the provided configuration. - /// - /// The configuration settings used to initialize the device. This parameter cannot be null. - /// An instance configured according to the specified . - EssentialsDevice BuildDevice(DeviceConfig deviceConfig); + /// + /// Builds and returns an instance based on the provided configuration. + /// + /// The configuration settings used to initialize the device. This parameter cannot be null. + /// An instance configured according to the specified . + EssentialsDevice BuildDevice(DeviceConfig deviceConfig); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Factory/IProcessorExtensionDeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/IProcessorExtensionDeviceFactory.cs index b0d56508..57870721 100644 --- a/src/PepperDash.Essentials.Core/Factory/IProcessorExtensionDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/IProcessorExtensionDeviceFactory.cs @@ -4,17 +4,16 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IProcessorExtensionDeviceFactory -/// -public interface IProcessorExtensionDeviceFactory +namespace PepperDash.Essentials.Core { /// - /// Loads all the extension factories to the ProcessorExtensionDeviceFactory + /// Defines the contract for IProcessorExtensionDeviceFactory /// - void LoadFactories(); + public interface IProcessorExtensionDeviceFactory + { + /// + /// Loads all the extension factories to the ProcessorExtensionDeviceFactory + /// + void LoadFactories(); + } } - diff --git a/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs b/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs index 5a082209..6336795c 100644 --- a/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Factory/ProcessorExtensionDeviceFactory.cs @@ -7,146 +7,165 @@ using System; using System.Collections.Generic; using System.Linq; -namespace PepperDash.Essentials.Core; - - -public class ProcessorExtensionDeviceFactory +namespace PepperDash.Essentials.Core { - public ProcessorExtensionDeviceFactory() { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); - var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory) - .IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + /// + /// Represents a ProcessorExtensionDeviceFactory + /// + public class ProcessorExtensionDeviceFactory + { + /// + /// Constructor + /// + public ProcessorExtensionDeviceFactory() { + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); - if (extensions != null ) - { - foreach ( var extension in extensions ) + var extensions = assy.GetTypes().Where(ct => typeof(IProcessorExtensionDeviceFactory) + .IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (extensions != null ) { - try + foreach ( var extension in extensions ) { - var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension); - factory.LoadFactories(); - } - catch( Exception e ) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name); + try + { + var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension); + factory.LoadFactories(); + } + catch( Exception e ) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load extension device: '{1}' ProcessorExtensionDeviceFactory: {0}", e, extension.Name); + } } } } - } - /// - /// A dictionary of factory methods, keyed by config types, added by plugins. - /// These methods are looked up and called by GetDevice in this class. - /// - static Dictionary ProcessorExtensionFactoryMethods = - new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// A dictionary of factory methods, keyed by config types, added by plugins. + /// These methods are looked up and called by GetDevice in this class. + /// + static Dictionary ProcessorExtensionFactoryMethods = + new Dictionary(StringComparer.OrdinalIgnoreCase); - /// - /// Adds a plugin factory method - /// - /// - /// - public static void AddFactoryForType(string extensionName, Func method) - { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method }); - } - - public static void AddFactoryForType(string extensionName, string description, Type Type, Func method) - { - //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); - - if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName)) + /// + /// Adds a plugin factory method + /// + /// name fo extension to add + /// method to add + /// + public static void AddFactoryForType(string extensionName, Func method) { - Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName); - return; + //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); + ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method }); } - var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; - ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper); - } - - private static void CheckForSecrets(IEnumerable obj) - { - foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null)) + /// + /// Adds a plugin factory method with type and description + /// + /// name of extension to add + /// description of extension to add + /// type of extension to add + /// method to add + public static void AddFactoryForType(string extensionName, string description, Type Type, Func method) { - if (prop.Name.ToLower() == "secret") + //Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName); + + if (ProcessorExtensionFactoryMethods.ContainsKey(extensionName)) { - var secret = GetSecret(prop.Children().First().ToObject()); - //var secret = GetSecret(JsonConvert.DeserializeObject(prop.Children().First().ToString())); - prop.Parent.Replace(secret); - } - var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject; - if (recurseProp == null) return; - CheckForSecrets(recurseProp.Properties()); - } - } - - private static string GetSecret(SecretsPropertiesConfig data) - { - var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); - if (secretProvider == null) return null; - var secret = secretProvider.GetSecret(data.Key); - if (secret != null) return (string)secret.Value; - Debug.LogMessage(LogEventLevel.Debug, - "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", - data.Provider, data.Key); - return String.Empty; - } - - /// - /// The factory method for processor extension devices. Also iterates the Factory methods that have - /// been loaded from plugins - /// - /// - /// - public static IKeyed GetExtensionDevice(DeviceConfig dc) - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type); - - var localDc = new DeviceConfig(dc); - - var key = localDc.Key; - var name = localDc.Name; - var type = localDc.Type; - var properties = localDc.Properties; - //var propRecurse = properties; - - var typeName = localDc.Type.ToLower(); - - var jObject = properties as Newtonsoft.Json.Linq.JObject; - if (jObject != null) - { - var jProp = jObject.Properties(); - - CheckForSecrets(jProp); + Debug.LogMessage(LogEventLevel.Information, "Unable to add extension device: '{0}'. Already exists in ProcessorExtensionDeviceFactory", extensionName); + return; } - Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); - // Check for types that have been added by plugin dlls. - return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc); + var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method }; + ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper); } - catch (Exception ex) + + private static void CheckForSecrets(IEnumerable obj) { - Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message); - - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace); - - if (ex.InnerException == null) + foreach (var prop in obj.Where(prop => prop.Value as Newtonsoft.Json.Linq.JObject != null)) { + if (prop.Name.ToLower() == "secret") + { + var secret = GetSecret(prop.Children().First().ToObject()); + //var secret = GetSecret(JsonConvert.DeserializeObject(prop.Children().First().ToString())); + prop.Parent.Replace(secret); + } + var recurseProp = prop.Value as Newtonsoft.Json.Linq.JObject; + if (recurseProp == null) return; + CheckForSecrets(recurseProp.Properties()); + } + } + + private static string GetSecret(SecretsPropertiesConfig data) + { + var secretProvider = SecretsManager.GetSecretProviderByKey(data.Provider); + if (secretProvider == null) return null; + var secret = secretProvider.GetSecret(data.Key); + if (secret != null) return (string)secret.Value; + Debug.LogMessage(LogEventLevel.Debug, + "Unable to retrieve secret {0}{1} - Make sure you've added it to the secrets provider", + data.Provider, data.Key); + return String.Empty; + } + + /// + /// The factory method for processor extension devices. Also iterates the Factory methods that have + /// been loaded from plugins + /// + /// + /// + /// + /// GetExtensionDevice method + /// + public static IKeyed GetExtensionDevice(DeviceConfig dc) + { + try + { + Debug.LogMessage(LogEventLevel.Information, "Loading '{0}' from Essentials Core", dc.Type); + + var localDc = new DeviceConfig(dc); + + var key = localDc.Key; + var name = localDc.Name; + var type = localDc.Type; + var properties = localDc.Properties; + //var propRecurse = properties; + + var typeName = localDc.Type.ToLower(); + + var jObject = properties as Newtonsoft.Json.Linq.JObject; + if (jObject != null) + { + var jProp = jObject.Properties(); + + CheckForSecrets(jProp); + } + + Debug.LogMessage(LogEventLevel.Verbose, "typeName = {0}", typeName); + // Check for types that have been added by plugin dlls. + return !ProcessorExtensionFactoryMethods.ContainsKey(typeName) ? null : ProcessorExtensionFactoryMethods[typeName].FactoryMethod(localDc); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Information, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message); + + Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace); + + if (ex.InnerException == null) + { + return null; + } + + Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key, + ex.InnerException.Message); + Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace); return null; } - - Debug.LogMessage(LogEventLevel.Information, "Inner exception while creating device {0}: {1}", dc.Key, - ex.InnerException.Message); - Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace); - return null; } + } } diff --git a/src/PepperDash.Essentials.Core/Factory/ReadyEventArgs.cs b/src/PepperDash.Essentials.Core/Factory/ReadyEventArgs.cs index 23fa1d1a..544ef5cc 100644 --- a/src/PepperDash.Essentials.Core/Factory/ReadyEventArgs.cs +++ b/src/PepperDash.Essentials.Core/Factory/ReadyEventArgs.cs @@ -1,39 +1,45 @@ using System; -namespace PepperDash.Essentials.Core; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Essentials.Core; - -/// -/// Represents a IsReadyEventArgs -/// -public class IsReadyEventArgs : EventArgs +namespace PepperDash.Essentials.Core { /// - /// Gets or sets the IsReady + /// Represents a IsReadyEventArgs /// - public bool IsReady { get; set; } + public class IsReadyEventArgs : EventArgs + { + /// + /// Gets or sets the IsReady + /// + public bool IsReady { get; set; } + + /// + /// Constructor + /// + /// indicates if the object is ready + public IsReadyEventArgs(bool data) + { + IsReady = data; + } + } /// - /// Constructor + /// Defines the contract for IHasReady /// - /// indicates if the object is ready - public IsReadyEventArgs(bool data) + public interface IHasReady { - IsReady = data; + /// + /// Fires when the IsReady property changes + /// + event EventHandler IsReadyEvent; + + /// + /// indicates whether the object is ready + /// + bool IsReady { get; } } } - -/// -/// Defines the contract for IHasReady -/// -public interface IHasReady -{ - /// - /// Fires when the IsReady property changes - /// - event EventHandler IsReadyEvent; - - /// - /// indicates whether the object is ready - /// - bool IsReady { get; } -} diff --git a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs index 3aa75e22..ea222b7f 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedback.cs @@ -5,165 +5,195 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro; -namespace PepperDash.Essentials.Core; - -/// -/// A Feedback whose output is derived from the return value of a provided Func. -/// -public class BoolFeedback : Feedback +namespace PepperDash.Essentials.Core { /// - /// Returns the current value of the feedback, derived from the ValueFunc. The ValueFunc is - /// evaluated whenever FireUpdate() is called + /// A Feedback whose output is derived from the return value of a provided Func. /// - public override bool BoolValue { get { return _BoolValue; } } - bool _BoolValue; - - /// - /// Fake value to be used in test mode - /// - public bool TestValue { get; private set; } - - /// - /// Func that evaluates on FireUpdate - /// - public Func ValueFunc { get; private set; } - - List LinkedInputSigs = new List(); - List LinkedComplementInputSigs = new List(); - - List LinkedCrestronFeedbacks = new List(); - - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Delegate to invoke when this feedback needs to be updated - public BoolFeedback(Func valueFunc) - : this(null, valueFunc) + public class BoolFeedback : Feedback { - } + /// + /// Returns the current value of the feedback, derived from the ValueFunc. The ValueFunc is + /// evaluated whenever FireUpdate() is called + /// + public override bool BoolValue { get { return _BoolValue; } } + bool _BoolValue; - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Key to find this Feedback - /// Delegate to invoke when this feedback needs to be updated - public BoolFeedback(string key, Func valueFunc) - : base(key) - { - ValueFunc = valueFunc; - } + /// + /// Fake value to be used in test mode + /// + public bool TestValue { get; private set; } - public void SetValueFunc(Func newFunc) - { - ValueFunc = newFunc; - } + /// + /// Gets or sets the ValueFunc + /// + public Func ValueFunc { get; private set; } - public override void FireUpdate() - { - bool newValue = InTestMode ? TestValue : ValueFunc.Invoke(); - if (newValue != _BoolValue) + List LinkedInputSigs = new List(); + List LinkedComplementInputSigs = new List(); + + List LinkedCrestronFeedbacks = new List(); + + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + public BoolFeedback(Func valueFunc) + : this(null, valueFunc) { - _BoolValue = newValue; - LinkedInputSigs.ForEach(s => UpdateSig(s)); - LinkedComplementInputSigs.ForEach(s => UpdateComplementSig(s)); - OnOutputChange(newValue); + } + + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Key to find this Feedback + /// Delegate to invoke when this feedback needs to be updated + public BoolFeedback(string key, Func valueFunc) + : base(key) + { + ValueFunc = valueFunc; + } + + /// + /// Sets the ValueFunc + /// + /// New function to set as the ValueFunc + public void SetValueFunc(Func newFunc) + { + ValueFunc = newFunc; + } + + /// + /// FireUpdate method + /// + /// + public override void FireUpdate() + { + bool newValue = InTestMode ? TestValue : ValueFunc.Invoke(); + if (newValue != _BoolValue) + { + _BoolValue = newValue; + LinkedInputSigs.ForEach(s => UpdateSig(s)); + LinkedComplementInputSigs.ForEach(s => UpdateComplementSig(s)); + OnOutputChange(newValue); + } + } + + /// + /// Links an input sig + /// + /// + /// + /// LinkInputSig method + /// + public void LinkInputSig(BoolInputSig sig) + { + LinkedInputSigs.Add(sig); + UpdateSig(sig); + } + + /// + /// Unlinks an inputs sig + /// + /// + /// + /// UnlinkInputSig method + /// + public void UnlinkInputSig(BoolInputSig sig) + { + LinkedInputSigs.Remove(sig); + } + + /// + /// Links an input sig to the complement value + /// + /// + public void LinkComplementInputSig(BoolInputSig sig) + { + LinkedComplementInputSigs.Add(sig); + UpdateComplementSig(sig); + } + + /// + /// Unlinks an input sig to the complement value + /// + /// + /// + /// UnlinkComplementInputSig method + /// + public void UnlinkComplementInputSig(BoolInputSig sig) + { + LinkedComplementInputSigs.Remove(sig); + } + + /// + /// Links a Crestron Feedback object + /// + /// + public void LinkCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) + { + LinkedCrestronFeedbacks.Add(feedback); + UpdateCrestronFeedback(feedback); + } + + /// + /// + /// + /// + /// + /// UnlinkCrestronFeedback method + /// + public void UnlinkCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) + { + LinkedCrestronFeedbacks.Remove(feedback); + } + + /// + /// ToString override + /// + /// + public override string ToString() + { + return (InTestMode ? "TEST -- " : "") + BoolValue.ToString(); + } + + /// + /// Puts this in test mode, sets the test value and fires an update. + /// + /// + /// + /// SetTestValue method + /// + public void SetTestValue(bool value) + { + TestValue = value; + InTestMode = true; + FireUpdate(); + } + + void UpdateSig(BoolInputSig sig) + { + sig.BoolValue = _BoolValue; + } + + void UpdateComplementSig(BoolInputSig sig) + { + sig.BoolValue = !_BoolValue; + } + + void UpdateCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) + { + feedback.State = _BoolValue; } } - /// - /// Links an input sig - /// - /// - public void LinkInputSig(BoolInputSig sig) - { - LinkedInputSigs.Add(sig); - UpdateSig(sig); - } - - /// - /// Unlinks an inputs sig - /// - /// - public void UnlinkInputSig(BoolInputSig sig) - { - LinkedInputSigs.Remove(sig); - } - - /// - /// Links an input sig to the complement value - /// - /// - public void LinkComplementInputSig(BoolInputSig sig) - { - LinkedComplementInputSigs.Add(sig); - UpdateComplementSig(sig); - } - - /// - /// Unlinks an input sig to the complement value - /// - /// - public void UnlinkComplementInputSig(BoolInputSig sig) - { - LinkedComplementInputSigs.Remove(sig); - } - - /// - /// Links a Crestron Feedback object - /// - /// - public void LinkCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) - { - LinkedCrestronFeedbacks.Add(feedback); - UpdateCrestronFeedback(feedback); - } - - /// - /// - /// - /// - public void UnlinkCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) - { - LinkedCrestronFeedbacks.Remove(feedback); - } - - public override string ToString() - { - return (InTestMode ? "TEST -- " : "") + BoolValue.ToString(); - } - - /// - /// Puts this in test mode, sets the test value and fires an update. - /// - /// - public void SetTestValue(bool value) - { - TestValue = value; - InTestMode = true; - FireUpdate(); - } - - void UpdateSig(BoolInputSig sig) - { - sig.BoolValue = _BoolValue; - } - - void UpdateComplementSig(BoolInputSig sig) - { - sig.BoolValue = !_BoolValue; - } - - void UpdateCrestronFeedback(Crestron.SimplSharpPro.DeviceSupport.Feedback feedback) - { - feedback.State = _BoolValue; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackOneShot.cs b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackOneShot.cs index 74341438..6b0b39b8 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackOneShot.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackOneShot.cs @@ -1,26 +1,31 @@ -using System.Timers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; namespace PepperDash.Essentials.Core { - /// - /// Represents a BoolFeedbackPulse - /// + /// + /// Represents a BoolFeedbackPulse + /// public class BoolFeedbackPulse { - /// - /// Gets or sets the TimeoutMs - /// + /// + /// Gets or sets the TimeoutMs + /// public uint TimeoutMs { get; set; } - /// - /// Gets or sets the CanRetrigger - /// + /// + /// Gets or sets the CanRetrigger + /// public bool CanRetrigger { get; set; } - /// - /// Gets or sets the Feedback - /// + /// + /// Gets or sets the Feedback + /// public BoolFeedback Feedback { get; private set; } - Timer Timer; + CTimer Timer; bool _BoolValue; @@ -51,35 +56,25 @@ namespace PepperDash.Essentials.Core { _BoolValue = true; Feedback.FireUpdate(); - Timer = new Timer(TimeoutMs) { AutoReset = false }; - Timer.Elapsed += (s, e) => + Timer = new CTimer(o => { _BoolValue = false; Feedback.FireUpdate(); Timer = null; - }; - Timer.Start(); + }, TimeoutMs); } // Timer is running, if retrigger is set, reset it. else if (CanRetrigger) - { - Timer.Stop(); - Timer.Interval = TimeoutMs; - Timer.Start(); - } + Timer.Reset(TimeoutMs); } - /// - /// Cancel method - /// + /// + /// Cancel method + /// public void Cancel() { - if (Timer != null) - { - Timer.Stop(); - Timer.Interval = 1; - Timer.Start(); - } + if(Timer != null) + Timer.Reset(0); } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackPulseExtender.cs b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackPulseExtender.cs index 207c6fd6..3a9dccde 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackPulseExtender.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/BoolFeedbackPulseExtender.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; -namespace PepperDash.Essentials.Core; - +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +namespace PepperDash.Essentials.Core +{ /// /// A class that wraps a BoolFeedback with logic that extends it's true state for /// a time period after the value goes false. @@ -20,7 +21,7 @@ namespace PepperDash.Essentials.Core; /// Gets the Feedback /// public BoolFeedback Feedback { get; private set; } - Timer Timer; + CTimer Timer; /// /// When set to true, will cause Feedback to go high, and cancel the timer. @@ -48,11 +49,7 @@ namespace PepperDash.Essentials.Core; else { if (Timer == null) - { - Timer = new Timer(TimeoutMs) { AutoReset = false }; - Timer.Elapsed += (s, e) => ClearFeedback(); - Timer.Start(); - } + Timer = new CTimer(o => ClearFeedback(), TimeoutMs); } } } @@ -84,4 +81,5 @@ namespace PepperDash.Essentials.Core; Feedback.FireUpdate(); Timer = null; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/BoolOutputLogicals.cs b/src/PepperDash.Essentials.Core/Feedbacks/BoolOutputLogicals.cs index 323a9be1..901b80dd 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/BoolOutputLogicals.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/BoolOutputLogicals.cs @@ -5,8 +5,8 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Abstract base class for BoolOutputLogicals @@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core; public void ClearOutputs() { OutputsIn.Clear(); - Evaluate(); + Evaluate(); } /// @@ -190,4 +190,5 @@ namespace PepperDash.Essentials.Core; ComputedValue = newValue; Output.FireUpdate(); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs index 7163764a..0c756ace 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackBase.cs @@ -1,6 +1,10 @@ using System; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Text; using Crestron.SimplSharp; +using Crestron.SimplSharpPro; + using PepperDash.Core; namespace PepperDash.Essentials.Core @@ -43,6 +47,12 @@ namespace PepperDash.Essentials.Core /// public bool InTestMode { get; protected set; } + /// + /// Base Constructor - empty + /// + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + protected Feedback() : this(null) { } + /// /// Constructor with Key parameter /// @@ -72,11 +82,11 @@ namespace PepperDash.Essentials.Core public abstract void FireUpdate(); /// - /// Fires the update asynchronously within a Task + /// Fires the update asynchronously within a CrestronInvoke /// public void InvokeFireUpdate() { - Task.Run(() => FireUpdate()); + CrestronInvoke.BeginInvoke(o => FireUpdate()); } /// @@ -110,4 +120,4 @@ namespace PepperDash.Essentials.Core if (OutputChange != null) OutputChange(this, new FeedbackEventArgs(value)); } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackCollection.cs b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackCollection.cs index aee11772..ed7517da 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackCollection.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackCollection.cs @@ -1,114 +1,24 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Linq; +using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Behaves like a List, with an indexer to find feedbacks by key name. Enforces unique feedback keys -/// (case-insensitive) - attempting to add a feedback whose key already exists in the collection throws an -/// . Feedbacks with no explicit key ( == "") are exempt -/// from the uniqueness check, since many feedbacks are intentionally constructed without a key and are not meant -/// to be looked up by the string indexer. -/// -/// -/// Derives from rather than so key-uniqueness can be enforced -/// via the virtual Insert/Set/Remove/Clear hooks (List{T}'s members are not virtual). Public surface area -/// (int indexer, Add/Insert/Remove/Clear, collection-initializer support, enumeration) is preserved; an -/// method is provided for source compatibility with existing List{T}.AddRange call sites. -/// -public class FeedbackCollection : Collection where T : Feedback +namespace PepperDash.Essentials.Core { - private readonly Dictionary _feedbacksByKey = new Dictionary(StringComparer.OrdinalIgnoreCase); - /// - /// Case-insensitive port lookup linked to feedbacks' keys + /// Basically a List , with an indexer to find feedbacks by key name /// - public T this[string key] + public class FeedbackCollection : List where T : Feedback { - get + /// + /// Case-insensitive port lookup linked to feedbacks' keys + /// + public T this[string key] { - return key != null && _feedbacksByKey.TryGetValue(key, out var feedback) ? feedback : null; - } - } - - /// - /// Adds a range of feedbacks to the collection. Provided for source compatibility with List{T}.AddRange. - /// - /// The feedbacks to add. - public void AddRange(IEnumerable items) - { - if (items == null) - throw new ArgumentNullException(nameof(items)); - - foreach (var item in items) - { - Add(item); - } - } - - /// - protected override void InsertItem(int index, T item) - { - ValidateNewItem(item); - base.InsertItem(index, item); - if (!string.IsNullOrEmpty(item.Key)) - { - _feedbacksByKey[item.Key] = item; - } - } - - /// - protected override void SetItem(int index, T item) - { - ValidateNewItem(item, indexBeingReplaced: index); - var replaced = Items[index]; - base.SetItem(index, item); - if (replaced != null && !string.IsNullOrEmpty(replaced.Key) && - !string.Equals(replaced.Key, item.Key, StringComparison.OrdinalIgnoreCase)) - { - _feedbacksByKey.Remove(replaced.Key); - } - if (!string.IsNullOrEmpty(item.Key)) - { - _feedbacksByKey[item.Key] = item; - } - } - - /// - protected override void RemoveItem(int index) - { - var removed = Items[index]; - base.RemoveItem(index); - if (removed != null && !string.IsNullOrEmpty(removed.Key)) - { - _feedbacksByKey.Remove(removed.Key); - } - } - - /// - protected override void ClearItems() - { - base.ClearItems(); - _feedbacksByKey.Clear(); - } - - private void ValidateNewItem(T item, int? indexBeingReplaced = null) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - - if (string.IsNullOrEmpty(item.Key)) - return; - - if (_feedbacksByKey.TryGetValue(item.Key, out var existing)) - { - var existingIndex = Items.IndexOf(existing); - if (existingIndex != indexBeingReplaced) + get { - throw new ArgumentException( - $"A Feedback with key '{item.Key}' already exists in this FeedbackCollection", nameof(item)); + return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); } } } diff --git a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackEventArgs.cs b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackEventArgs.cs index d2c1a815..e22e9366 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/FeedbackEventArgs.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/FeedbackEventArgs.cs @@ -4,96 +4,93 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a FeedbackEventArgs -/// -public class FeedbackEventArgs : EventArgs +namespace PepperDash.Essentials.Core { /// - /// Gets or sets the BoolValue + /// Represents a FeedbackEventArgs /// - public bool BoolValue { get; private set; } - - /// - /// Gets or sets the IntValue - /// - public int IntValue { get; private set; } - - /// - /// Gets or sets the UShortValue - /// - public ushort UShortValue + public class FeedbackEventArgs : EventArgs { - get + /// + /// Gets or sets the BoolValue + /// + public bool BoolValue { get; private set; } + + /// + /// Gets or sets the IntValue + /// + public int IntValue { get; private set; } + + /// + /// Gets or sets the UShortValue + /// + public ushort UShortValue { - return (ushort)IntValue; + get + { + return (ushort)IntValue; + } + } + + /// + /// Gets or sets the StringValue + /// + public string StringValue { get; private set; } + + /// + /// Gets or sets the Type + /// + public eFeedbackEventType Type { get; private set; } + + /// + /// Constructor for BoolValue + /// + /// value to set + public FeedbackEventArgs(bool value) + { + BoolValue = value; + Type = eFeedbackEventType.TypeBool; + } + + /// + /// Constructor for IntValue + /// + /// value to set + public FeedbackEventArgs(int value) + { + IntValue = value; + Type = eFeedbackEventType.TypeInt; + } + + /// + /// Constructor for StringValue + /// + /// value to set + public FeedbackEventArgs(string value) + { + StringValue = value; + Type = eFeedbackEventType.TypeString; } } /// - /// Gets or sets the StringValue + /// Enumeration of eFeedbackEventType values /// - public string StringValue { get; private set; } - - /// - /// Gets or sets the Type - /// - public eFeedbackEventType Type { get; private set; } - - /// - /// Constructor for BoolValue - /// - /// value to set - public FeedbackEventArgs(bool value) + public enum eFeedbackEventType { - BoolValue = value; - Type = eFeedbackEventType.TypeBool; + /// + /// Boolean type feedback event + /// + TypeBool, + + /// + /// Integer type feedback event + /// + TypeInt, + + /// + /// String type feedback event + /// + TypeString } - - /// - /// Constructor for IntValue - /// - /// value to set - public FeedbackEventArgs(int value) - { - IntValue = value; - Type = eFeedbackEventType.TypeInt; - } - - /// - /// Constructor for StringValue - /// - /// value to set - public FeedbackEventArgs(string value) - { - StringValue = value; - Type = eFeedbackEventType.TypeString; - } -} - -/// -/// Enumeration of eFeedbackEventType values -/// -public enum eFeedbackEventType -{ - /// - /// Boolean type feedback event - /// - TypeBool, - - /// - /// Integer type feedback event - /// - TypeInt, - - /// - /// String type feedback event - /// - TypeString -} - - - - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs index 1cf7a3bd..4dd0424b 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/IntFeedback.cs @@ -5,98 +5,132 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro; -namespace PepperDash.Essentials.Core; - -public class IntFeedback : Feedback +namespace PepperDash.Essentials.Core { - public override int IntValue { get { return _IntValue; } } // ValueFunc.Invoke(); } } - int _IntValue; - public ushort UShortValue { get { return (ushort)_IntValue; } } - - //public override eCueType Type { get { return eCueType.Int; } } - - public int TestValue { get; private set; } - /// - /// Func evaluated on FireUpdate + /// Represents a IntFeedback /// - Func ValueFunc; - List LinkedInputSigs = new List(); - - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Delegate to invoke when this feedback needs to be updated - public IntFeedback(Func valueFunc) - : this(null, valueFunc) + public class IntFeedback : Feedback { - } + /// + /// Gets or sets the IntValue + /// + public override int IntValue { get { return _IntValue; } } // ValueFunc.Invoke(); } } + int _IntValue; + /// + /// Gets or sets the UShortValue + /// + public ushort UShortValue { get { return (ushort)_IntValue; } } - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Key to find this Feedback - /// Delegate to invoke when this feedback needs to be updated - public IntFeedback(string key, Func valueFunc) - : base(key) - { - ValueFunc = valueFunc; - } + //public override eCueType Type { get { return eCueType.Int; } } - public void SetValueFunc(Func newFunc) - { - ValueFunc = newFunc; - } + /// + /// Gets or sets the TestValue + /// + public int TestValue { get; private set; } + /// + /// Func evaluated on FireUpdate + /// + Func ValueFunc; + List LinkedInputSigs = new List(); - public override void FireUpdate() - { - var newValue = InTestMode ? TestValue : ValueFunc.Invoke(); - if (newValue != _IntValue) + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + public IntFeedback(Func valueFunc) + : this(null, valueFunc) { - _IntValue = newValue; - LinkedInputSigs.ForEach(s => UpdateSig(s)); - OnOutputChange(newValue); + } + + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Key to find this Feedback + /// Delegate to invoke when this feedback needs to be updated + public IntFeedback(string key, Func valueFunc) + : base(key) + { + ValueFunc = valueFunc; + } + + /// + /// Sets the ValueFunc + /// + /// function to set + public void SetValueFunc(Func newFunc) + { + ValueFunc = newFunc; + } + + + /// + /// FireUpdate method + /// + /// + public override void FireUpdate() + { + var newValue = InTestMode ? TestValue : ValueFunc.Invoke(); + if (newValue != _IntValue) + { + _IntValue = newValue; + LinkedInputSigs.ForEach(s => UpdateSig(s)); + OnOutputChange(newValue); + } + } + + /// + /// LinkInputSig method + /// + public void LinkInputSig(UShortInputSig sig) + { + LinkedInputSigs.Add(sig); + UpdateSig(sig); + } + + /// + /// UnlinkInputSig method + /// + public void UnlinkInputSig(UShortInputSig sig) + { + LinkedInputSigs.Remove(sig); + } + + /// + /// ToString method + /// + public override string ToString() + { + return (InTestMode ? "TEST -- " : "") + IntValue.ToString(); + } + + /// + /// Puts this in test mode, sets the test value and fires an update. + /// + /// + /// + /// SetTestValue method + /// + public void SetTestValue(int value) + { + TestValue = value; + InTestMode = true; + FireUpdate(); + } + + void UpdateSig(UShortInputSig sig) + { + sig.UShortValue = UShortValue; } } - - public void LinkInputSig(UShortInputSig sig) - { - LinkedInputSigs.Add(sig); - UpdateSig(sig); - } - - public void UnlinkInputSig(UShortInputSig sig) - { - LinkedInputSigs.Remove(sig); - } - - public override string ToString() - { - return (InTestMode ? "TEST -- " : "") + IntValue.ToString(); - } - - /// - /// Puts this in test mode, sets the test value and fires an update. - /// - /// - public void SetTestValue(int value) - { - TestValue = value; - InTestMode = true; - FireUpdate(); - } - - void UpdateSig(UShortInputSig sig) - { - sig.UShortValue = UShortValue; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs index 0b4ef534..1bde5a08 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/SerialFeedback.cs @@ -5,86 +5,112 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro; -namespace PepperDash.Essentials.Core; - -/// -/// To be used for serial data feedback where the event chain / asynchronicity must be maintained -/// and calculating the value based on a Func when it is needed will not suffice. -/// -public class SerialFeedback : Feedback +namespace PepperDash.Essentials.Core { - - /// - public override string SerialValue { get { return _SerialValue; } } - string _SerialValue; - - //public override eCueType Type { get { return eCueType.Serial; } } - /// - /// Used in testing. Set/Clear functions + /// To be used for serial data feedback where the event chain / asynchronicity must be maintained + /// and calculating the value based on a Func when it is needed will not suffice. /// - public string TestValue { get; private set; } - - List LinkedInputSigs = new List(); - - - /// - public SerialFeedback(string key) - : base(key) + public class SerialFeedback : Feedback { - } + /// + /// Gets the SerialValue + /// + public override string SerialValue { get { return _SerialValue; } } + string _SerialValue; - /// - public override void FireUpdate() - { - throw new NotImplementedException("This feedback type does not use Funcs"); - } + //public override eCueType Type { get { return eCueType.Serial; } } - /// - public void FireUpdate(string newValue) - { - _SerialValue = newValue; - LinkedInputSigs.ForEach(s => UpdateSig(s, newValue)); - OnOutputChange(newValue); - } + /// + /// Gets or sets the TestValue + /// + public string TestValue { get; private set; } - /// - public void LinkInputSig(StringInputSig sig) - { - LinkedInputSigs.Add(sig); - UpdateSig(sig); - } + List LinkedInputSigs = new List(); - /// - public void UnlinkInputSig(StringInputSig sig) - { - LinkedInputSigs.Remove(sig); - } + /// + /// Constructor + /// + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + public SerialFeedback() + { + } - /// - public override string ToString() - { - return (InTestMode ? "TEST -- " : "") + SerialValue; - } + /// + /// Constructor with Key parameter + /// + /// Key to find this Feedback + public SerialFeedback(string key) + : base(key) + { + } - /// - /// Puts this in test mode, sets the test value and fires an update. - /// - /// - public void SetTestValue(string value) - { - TestValue = value; - InTestMode = true; - FireUpdate(TestValue); - } + /// + /// FireUpdate method + /// + /// + public override void FireUpdate() + { + throw new NotImplementedException("This feedback type does not use Funcs"); + } - void UpdateSig(StringInputSig sig) - { - sig.StringValue = _SerialValue; - } + /// + /// FireUpdate method + /// + public void FireUpdate(string newValue) + { + _SerialValue = newValue; + LinkedInputSigs.ForEach(s => UpdateSig(s, newValue)); + OnOutputChange(newValue); + } - void UpdateSig(StringInputSig sig, string value) - { - sig.StringValue = value; + /// + /// LinkInputSig method + /// + public void LinkInputSig(StringInputSig sig) + { + LinkedInputSigs.Add(sig); + UpdateSig(sig); + } + + /// + /// UnlinkInputSig method + /// + public void UnlinkInputSig(StringInputSig sig) + { + LinkedInputSigs.Remove(sig); + } + + /// + /// ToString method + /// + public override string ToString() + { + return (InTestMode ? "TEST -- " : "") + SerialValue; + } + + /// + /// Puts this in test mode, sets the test value and fires an update. + /// + /// + /// + /// SetTestValue method + /// + public void SetTestValue(string value) + { + TestValue = value; + InTestMode = true; + FireUpdate(TestValue); + } + + void UpdateSig(StringInputSig sig) + { + sig.StringValue = _SerialValue; + } + + void UpdateSig(StringInputSig sig, string value) + { + sig.StringValue = value; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs b/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs index 2eb18414..4c419903 100644 --- a/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs +++ b/src/PepperDash.Essentials.Core/Feedbacks/StringFeedback.cs @@ -5,98 +5,126 @@ using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro; -namespace PepperDash.Essentials.Core; - - -public class StringFeedback : Feedback +namespace PepperDash.Essentials.Core { - public override string StringValue { get { return _StringValue; } } // ValueFunc.Invoke(); } } - string _StringValue; /// - /// Used in testing. Set/Clear functions + /// Represents a StringFeedback /// - public string TestValue { get; private set; } - - /// - /// Evaluated on FireUpdate - /// - public Func ValueFunc { get; private set; } - List LinkedInputSigs = new List(); - - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Delegate to invoke when this feedback needs to be updated - public StringFeedback(Func valueFunc) - : this(null, valueFunc) + public class StringFeedback : Feedback { - } + /// + /// Gets or sets the StringValue + /// + public override string StringValue { get { return _StringValue; } } // ValueFunc.Invoke(); } } + string _StringValue; - /// - /// Creates the feedback with the Func as described. - /// - /// - /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, - /// it will NOT reflect an actual value from a device until has been called - /// - /// Key to find this Feedback - /// Delegate to invoke when this feedback needs to be updated - public StringFeedback(string key, Func valueFunc) - : base(key) - { - ValueFunc = valueFunc; - } + /// + /// Gets or sets the TestValue + /// + public string TestValue { get; private set; } - public void SetValueFunc(Func newFunc) - { - ValueFunc = newFunc; - } + /// + /// Gets or sets the ValueFunc + /// + public Func ValueFunc { get; private set; } + List LinkedInputSigs = new List(); - public override void FireUpdate() - { - var newValue = InTestMode ? TestValue : ValueFunc.Invoke(); - if (newValue != _StringValue) + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Delegate to invoke when this feedback needs to be updated + [Obsolete("use constructor with Key parameter. This constructor will be removed in a future version")] + public StringFeedback(Func valueFunc) + : this(null, valueFunc) { - _StringValue = newValue; - LinkedInputSigs.ForEach(s => UpdateSig(s)); - OnOutputChange(newValue); + } + + /// + /// Creates the feedback with the Func as described. + /// + /// + /// While the linked sig value will be updated with the current value stored when it is linked to a EISC Bridge, + /// it will NOT reflect an actual value from a device until has been called + /// + /// Key to find this Feedback + /// Delegate to invoke when this feedback needs to be updated + public StringFeedback(string key, Func valueFunc) + : base(key) + { + ValueFunc = valueFunc; + } + + /// + /// Sets the ValueFunc + /// + /// function to set + public void SetValueFunc(Func newFunc) + { + ValueFunc = newFunc; + } + + /// + /// FireUpdate method + /// + /// + public override void FireUpdate() + { + var newValue = InTestMode ? TestValue : ValueFunc.Invoke(); + if (newValue != _StringValue) + { + _StringValue = newValue; + LinkedInputSigs.ForEach(s => UpdateSig(s)); + OnOutputChange(newValue); + } + } + + /// + /// LinkInputSig method + /// + public void LinkInputSig(StringInputSig sig) + { + LinkedInputSigs.Add(sig); + UpdateSig(sig); + } + + /// + /// UnlinkInputSig method + /// + public void UnlinkInputSig(StringInputSig sig) + { + LinkedInputSigs.Remove(sig); + } + + /// + /// ToString method + /// + public override string ToString() + { + return (InTestMode ? "TEST -- " : "") + StringValue; + } + + /// + /// Puts this in test mode, sets the test value and fires an update. + /// + /// + /// + /// SetTestValue method + /// + public void SetTestValue(string value) + { + TestValue = value; + InTestMode = true; + FireUpdate(); + } + + void UpdateSig(StringInputSig sig) + { + sig.StringValue = _StringValue; } } - - public void LinkInputSig(StringInputSig sig) - { - LinkedInputSigs.Add(sig); - UpdateSig(sig); - } - - public void UnlinkInputSig(StringInputSig sig) - { - LinkedInputSigs.Remove(sig); - } - - public override string ToString() - { - return (InTestMode ? "TEST -- " : "") + StringValue; - } - - /// - /// Puts this in test mode, sets the test value and fires an update. - /// - /// - public void SetTestValue(string value) - { - TestValue = value; - InTestMode = true; - FireUpdate(); - } - - void UpdateSig(StringInputSig sig) - { - sig.StringValue = _StringValue; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/File/FileIO.cs b/src/PepperDash.Essentials.Core/File/FileIO.cs index 7d1fe721..df1c0750 100644 --- a/src/PepperDash.Essentials.Core/File/FileIO.cs +++ b/src/PepperDash.Essentials.Core/File/FileIO.cs @@ -1,10 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using PepperDash.Core; +using Crestron.SimplSharpPro.CrestronThread; using Serilog.Events; namespace PepperDash.Essentials.Core @@ -15,7 +16,7 @@ namespace PepperDash.Essentials.Core public static class FileIO { - static readonly object _fileLock = new(); + static CCriticalSection fileLock = new CCriticalSection(); /// /// Delegate for GotFileEventHandler /// @@ -33,42 +34,43 @@ namespace PepperDash.Essentials.Core /// /// /// - public static FileInfo[] GetFiles(string fileName) - { - string fullFilePath = Global.FilePathPrefix + fileName; - DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(fullFilePath)); - var files = dirInfo.GetFiles(Path.GetFileName(fullFilePath)); - Debug.LogMessage(LogEventLevel.Information, "FileIO found: {0}, {1}", files.Count(), fullFilePath); - if (files.Count() > 0) - { - return files; - } - else - { - return null; - } - } + /// + /// GetFiles method + /// + public static FileInfo[] GetFiles(string fileName) + { + string fullFilePath = Global.FilePathPrefix + fileName; + DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(fullFilePath)); + var files = dirInfo.GetFiles(Path.GetFileName(fullFilePath)); + Debug.LogMessage(LogEventLevel.Information, "FileIO found: {0}, {1}", files.Count(), fullFilePath); + if (files.Count() > 0) + { + return files; + } + else + { + return null; + } + } - /// - /// Get the full file info from a path/filename, can include wildcards. - /// - /// - /// - public static FileInfo GetFile(string fileName) - { - string fullFilePath = Global.FilePathPrefix + fileName; - DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(fullFilePath)); - var files = dirInfo.GetFiles(Path.GetFileName(fullFilePath)); - Debug.LogMessage(LogEventLevel.Information, "FileIO found: {0}, {1}", files.Count(), fullFilePath); - if (files.Count() > 0) - { - return files.FirstOrDefault(); - } - else - { - return null; - } - } + /// + /// GetFile method + /// + public static FileInfo GetFile(string fileName) + { + string fullFilePath = Global.FilePathPrefix + fileName; + DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(fullFilePath)); + var files = dirInfo.GetFiles(Path.GetFileName(fullFilePath)); + Debug.LogMessage(LogEventLevel.Information, "FileIO found: {0}, {1}", files.Count(), fullFilePath); + if (files.Count() > 0) + { + return files.FirstOrDefault(); + } + else + { + return null; + } + } /// @@ -76,9 +78,9 @@ namespace PepperDash.Essentials.Core /// /// /// - /// - /// ReadDataFromFile method - /// + /// + /// ReadDataFromFile method + /// public static string ReadDataFromFile(string fileName) { try @@ -92,21 +94,19 @@ namespace PepperDash.Essentials.Core } } - /// - /// Get the data with fileInfo object - /// - /// - /// - /// - /// ReadDataFromFile method - /// - public static string ReadDataFromFile(FileInfo file) + /// + /// Get the data with fileInfo object + /// + /// + /// + /// + /// ReadDataFromFile method + /// + public static string ReadDataFromFile(FileInfo file) { - var lockAcquired = false; try { - lockAcquired = Monitor.TryEnter(_fileLock); - if (lockAcquired) + if (fileLock.TryEnter()) { DirectoryInfo dirInfo = new DirectoryInfo(file.DirectoryName); Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName); @@ -129,6 +129,7 @@ namespace PepperDash.Essentials.Core Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock"); return ""; } + } catch (Exception e) { @@ -137,15 +138,16 @@ namespace PepperDash.Essentials.Core } finally { - if (lockAcquired) - Monitor.Exit(_fileLock); + if (fileLock != null && !fileLock.Disposed) + fileLock.Leave(); + } } - /// - /// ReadDataFromFileASync method - /// + /// + /// ReadDataFromFileASync method + /// public static void ReadDataFromFileASync(string fileName) { try @@ -158,14 +160,14 @@ namespace PepperDash.Essentials.Core } } - /// - /// ReadDataFromFileASync method - /// + /// + /// ReadDataFromFileASync method + /// public static void ReadDataFromFileASync(FileInfo file) { try { - Task.Run(() => _ReadDataFromFileASync(file)); + CrestronInvoke.BeginInvoke(o => _ReadDataFromFileASync(file)); } catch (Exception e) { @@ -176,11 +178,9 @@ namespace PepperDash.Essentials.Core private static void _ReadDataFromFileASync(FileInfo file) { string data; - var lockAcquired = false; try { - lockAcquired = Monitor.TryEnter(_fileLock); - if (lockAcquired) + if (fileLock.TryEnter()) { DirectoryInfo dirInfo = new DirectoryInfo(file.Name); Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName); @@ -213,9 +213,13 @@ namespace PepperDash.Essentials.Core } finally { - if (lockAcquired) - Monitor.Exit(_fileLock); + if (fileLock != null && !fileLock.Disposed) + fileLock.Leave(); + } + + + } /// @@ -225,35 +229,35 @@ namespace PepperDash.Essentials.Core /// public static void WriteDataToFile(string data, string filePath) { - var _WriteFileThread = new System.Threading.Thread(() => _WriteFileMethod(data, Global.FilePathPrefix + "/" + filePath)) - { - IsBackground = true, - Priority = ThreadPriority.Lowest - }; + Thread _WriteFileThread; + _WriteFileThread = new Thread((O) => _WriteFileMethod(data, Global.FilePathPrefix + "/" + filePath), null, Thread.eThreadStartOptions.CreateSuspended); + _WriteFileThread.Priority = Thread.eThreadPriority.LowestPriority; _WriteFileThread.Start(); Debug.LogMessage(LogEventLevel.Information, "New WriteFile Thread"); + } - static void _WriteFileMethod(string data, string filePath) + static object _WriteFileMethod(string data, string filePath) { Debug.LogMessage(LogEventLevel.Information, "Attempting to write file: '{0}'", filePath); - var lockAcquired = false; try { - lockAcquired = Monitor.TryEnter(_fileLock); - if (lockAcquired) + if (fileLock.TryEnter()) { - using (StreamWriter sw = new StreamWriter(filePath)) + + using (StreamWriter sw = new StreamWriter(filePath)) { sw.Write(data); sw.Flush(); } + } else { Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock"); } + } catch (Exception e) { @@ -261,25 +265,28 @@ namespace PepperDash.Essentials.Core } finally { - if (lockAcquired) - Monitor.Exit(_fileLock); + if (fileLock != null && !fileLock.Disposed) + fileLock.Leave(); + } + return null; + } /// /// /// /// - /// - /// FileIoUnitTest method - /// + /// + /// FileIoUnitTest method + /// public static bool FileIoUnitTest() { var testData = "Testing FileIO"; FileIO.WriteDataToFile(testData, "\\user\\FileIOTest.pdt"); var file = FileIO.GetFile("\\user\\*FileIOTest*"); - + var readData = FileIO.ReadDataFromFile(file); Debug.LogMessage(LogEventLevel.Information, "Returned {0}", readData); File.Delete(file.FullName); @@ -294,9 +301,9 @@ namespace PepperDash.Essentials.Core } } - /// - /// Represents a FileEventArgs - /// + /// + /// Represents a FileEventArgs + /// public class FileEventArgs { /// @@ -304,9 +311,9 @@ namespace PepperDash.Essentials.Core /// /// public FileEventArgs(string data) { Data = data; } - /// - /// Gets or sets the Data - /// + /// + /// Gets or sets the Data + /// public string Data { get; private set; } // readonly } diff --git a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs index d31f4cdf..ba638623 100644 --- a/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs +++ b/src/PepperDash.Essentials.Core/Fusion/EssentialsHuddleSpaceRoomFusionRoomJoinMap.cs @@ -7,227 +7,227 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core.Bridges; -namespace PepperDash.Essentials.Core.Fusion; - -/// -/// Represents a EssentialsHuddleSpaceRoomFusionRoomJoinMap -/// -public class EssentialsHuddleSpaceRoomFusionRoomJoinMap : JoinMapBaseAdvanced +namespace PepperDash.Essentials.Core.Fusion { - - // Processor Attributes /// - /// Processor IP 1 + /// Represents a EssentialsHuddleSpaceRoomFusionRoomJoinMap /// - [JoinName("ProcessorIp1")] - public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" }, - new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor IP 2 - /// - [JoinName("ProcessorIp2")] - public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" }, - new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Gateway - /// - [JoinName("ProcessorGateway")] - public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" }, - new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Hostname - /// - [JoinName("ProcessorHostname")] - public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" }, - new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Domain - /// - [JoinName("ProcessorDomain")] - public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" }, - new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor DNS 1 - /// - [JoinName("ProcessorDns1")] - public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" }, - new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor DNS 2 - /// - [JoinName("ProcessorDns2")] - public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" }, - new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor MAC 1 - /// - [JoinName("ProcessorMac1")] - public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" }, - new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor MAC 2 - /// - [JoinName("ProcessorMac2")] - public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" }, - new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Net Mask 1 - /// - [JoinName("ProcessorNetMask1")] - public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" }, - new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Net Mask 2 - /// - [JoinName("ProcessorNetMask2")] - public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" }, - new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Firmware - /// - [JoinName("ProcessorFirmware")] - public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" }, - new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Program Name Start - /// - [JoinName("ProgramNameStart")] - public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" }, - new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// Processor Reboot - /// - [JoinName("ProcessorReboot")] - public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" }, - new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital }); - - // Volume Controls - /// - /// Volume Fader 1 - /// - [JoinName("VolumeFader1")] - public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" }, - new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog }); - - // Codec Info - /// - /// VC Codec In Call - /// - [JoinName("VcCodecInCall")] - public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" }, - new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); - - /// - /// VC Codec Online - /// - [JoinName("VcCodecOnline")] - public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" }, - new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); - - /// - /// VC Codec IP Address - /// - [JoinName("VcCodecIpAddress")] - public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" }, - new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - /// - /// VC Codec IP Port - /// - [JoinName("VcCodecIpPort")] - public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" }, - new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - // Source Attributes - /// - /// Display 1 Current Source Name - /// - [JoinName("Display1CurrentSourceName")] - public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" }, - new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - - - // Device Online Status - /// - /// Touchpanel Online Start - /// - [JoinName("TouchpanelOnlineStart")] - public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" }, - new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); - - /// - /// Xpanel Online Start - /// - [JoinName("XpanelOnlineStart")] - public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" }, - new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); - - /// - /// Display Online Start - /// - [JoinName("DisplayOnlineStart")] - public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" }, - new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); - - /// - /// Display 1 Laptop Source Start - /// - [JoinName("Display1LaptopSourceStart")] - public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 165, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" }, - new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); - - /// - /// Display 1 Disc Player Source Start - /// - [JoinName("Display1DiscPlayerSourceStart")] - public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 180, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" }, - new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); - - /// - /// Display 1 Set Top Box Source Start - /// - [JoinName("Display1SetTopBoxSourceStart")] - public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 185, JoinSpan = 5, AttributeName = "Display 1 - Source TV" }, - new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); - - // Display 1 - /// - /// Display 1 Start - /// - [JoinName("Display1Start")] - public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 190, JoinSpan = 1 }, - new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); - - /// - /// Constructor to use when instantiating this Join Map without inheriting from it - /// - /// Join this join map will start at - public EssentialsHuddleSpaceRoomFusionRoomJoinMap(uint joinStart) - : base(joinStart, typeof(EssentialsHuddleSpaceRoomFusionRoomJoinMap)) + public class EssentialsHuddleSpaceRoomFusionRoomJoinMap : JoinMapBaseAdvanced { - } + // Processor Attributes + /// + /// Processor IP 1 + /// + [JoinName("ProcessorIp1")] + public JoinDataComplete ProcessorIp1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Info - Processor - IP 1" }, + new JoinMetadata { Description = "Info - Processor - IP 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); - /// - /// Constructor to use when extending this Join map - /// - /// Join this join map will start at - /// Type of the child join map - public EssentialsHuddleSpaceRoomFusionRoomJoinMap(uint joinStart, Type type) : base(joinStart, type) - { - } -} + /// + /// Processor IP 2 + /// + [JoinName("ProcessorIp2")] + public JoinDataComplete ProcessorIp2 = new JoinDataComplete(new JoinData { JoinNumber = 51, JoinSpan = 1, AttributeName = "Info - Processor - IP 2" }, + new JoinMetadata { Description = "Info - Processor - IP 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + /// + /// Processor Gateway + /// + [JoinName("ProcessorGateway")] + public JoinDataComplete ProcessorGateway = new JoinDataComplete(new JoinData { JoinNumber = 52, JoinSpan = 1, AttributeName = "Info - Processor - Gateway" }, + new JoinMetadata { Description = "Info - Processor - Gateway", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Hostname + /// + [JoinName("ProcessorHostname")] + public JoinDataComplete ProcessorHostname = new JoinDataComplete(new JoinData { JoinNumber = 53, JoinSpan = 1, AttributeName = "Info - Processor - Hostname" }, + new JoinMetadata { Description = "Info - Processor - Hostname", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Domain + /// + [JoinName("ProcessorDomain")] + public JoinDataComplete ProcessorDomain = new JoinDataComplete(new JoinData { JoinNumber = 54, JoinSpan = 1, AttributeName = "Info - Processor - Domain" }, + new JoinMetadata { Description = "Info - Processor - Domain", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor DNS 1 + /// + [JoinName("ProcessorDns1")] + public JoinDataComplete ProcessorDns1 = new JoinDataComplete(new JoinData { JoinNumber = 55, JoinSpan = 1, AttributeName = "Info - Processor - DNS 1" }, + new JoinMetadata { Description = "Info - Processor - DNS 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor DNS 2 + /// + [JoinName("ProcessorDns2")] + public JoinDataComplete ProcessorDns2 = new JoinDataComplete(new JoinData { JoinNumber = 56, JoinSpan = 1, AttributeName = "Info - Processor - DNS 2" }, + new JoinMetadata { Description = "Info - Processor - DNS 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor MAC 1 + /// + [JoinName("ProcessorMac1")] + public JoinDataComplete ProcessorMac1 = new JoinDataComplete(new JoinData { JoinNumber = 57, JoinSpan = 1, AttributeName = "Info - Processor - MAC 1" }, + new JoinMetadata { Description = "Info - Processor - MAC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor MAC 2 + /// + [JoinName("ProcessorMac2")] + public JoinDataComplete ProcessorMac2 = new JoinDataComplete(new JoinData { JoinNumber = 58, JoinSpan = 1, AttributeName = "Info - Processor - MAC 2" }, + new JoinMetadata { Description = "Info - Processor - MAC 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Net Mask 1 + /// + [JoinName("ProcessorNetMask1")] + public JoinDataComplete ProcessorNetMask1 = new JoinDataComplete(new JoinData { JoinNumber = 59, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 1" }, + new JoinMetadata { Description = "Info - Processor - Net Mask 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Net Mask 2 + /// + [JoinName("ProcessorNetMask2")] + public JoinDataComplete ProcessorNetMask2 = new JoinDataComplete(new JoinData { JoinNumber = 60, JoinSpan = 1, AttributeName = "Info - Processor - Net Mask 2" }, + new JoinMetadata { Description = "Info - Processor - Net Mask 2", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Firmware + /// + [JoinName("ProcessorFirmware")] + public JoinDataComplete ProcessorFirmware = new JoinDataComplete(new JoinData { JoinNumber = 61, JoinSpan = 1, AttributeName = "Info - Processor - Firmware" }, + new JoinMetadata { Description = "Info - Processor - Firmware", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Program Name Start + /// + [JoinName("ProgramNameStart")] + public JoinDataComplete ProgramNameStart = new JoinDataComplete(new JoinData { JoinNumber = 62, JoinSpan = 10, AttributeName = "Info - Processor - Program" }, + new JoinMetadata { Description = "Info - Processor - Program", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// Processor Reboot + /// + [JoinName("ProcessorReboot")] + public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 74, JoinSpan = 1, AttributeName = "Processor - Reboot" }, + new JoinMetadata { Description = "Processor - Reboot", JoinCapabilities = eJoinCapabilities.FromFusion, JoinType = eJoinType.Digital }); + + // Volume Controls + /// + /// Volume Fader 1 + /// + [JoinName("VolumeFader1")] + public JoinDataComplete VolumeFader1 = new JoinDataComplete(new JoinData { JoinNumber = 50, JoinSpan = 1, AttributeName = "Volume - Fader01" }, + new JoinMetadata { Description = "Volume - Fader01", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Analog }); + + // Codec Info + /// + /// VC Codec In Call + /// + [JoinName("VcCodecInCall")] + public JoinDataComplete VcCodecInCall = new JoinDataComplete(new JoinData { JoinNumber = 69, JoinSpan = 1, AttributeName = "Conf - VC 1 In Call" }, + new JoinMetadata { Description = "Conf - VC 1 In Call", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + + /// + /// VC Codec Online + /// + [JoinName("VcCodecOnline")] + public JoinDataComplete VcCodecOnline = new JoinDataComplete(new JoinData { JoinNumber = 122, JoinSpan = 1, AttributeName = "Online - VC 1" }, + new JoinMetadata { Description = "Online - VC 1", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + + /// + /// VC Codec IP Address + /// + [JoinName("VcCodecIpAddress")] + public JoinDataComplete VcCodecIpAddress = new JoinDataComplete(new JoinData { JoinNumber = 121, JoinSpan = 1, AttributeName = "IP Address - VC" }, + new JoinMetadata { Description = "IP Address - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + /// + /// VC Codec IP Port + /// + [JoinName("VcCodecIpPort")] + public JoinDataComplete VcCodecIpPort = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 1, AttributeName = "IP Port - VC" }, + new JoinMetadata { Description = "IP Port - VC", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + // Source Attributes + /// + /// Display 1 Current Source Name + /// + [JoinName("Display1CurrentSourceName")] + public JoinDataComplete Display1CurrentSourceName = new JoinDataComplete(new JoinData { JoinNumber = 84, JoinSpan = 1, AttributeName = "Display 1 - Current Source" }, + new JoinMetadata { Description = "Display 1 - Current Source", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Serial }); + + + // Device Online Status + /// + /// Touchpanel Online Start + /// + [JoinName("TouchpanelOnlineStart")] + public JoinDataComplete TouchpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 150, JoinSpan = 10, AttributeName = "Online - Touch Panel" }, + new JoinMetadata { Description = "Online - Touch Panel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + + /// + /// Xpanel Online Start + /// + [JoinName("XpanelOnlineStart")] + public JoinDataComplete XpanelOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 160, JoinSpan = 5, AttributeName = "Online - XPanel" }, + new JoinMetadata { Description = "Online - XPanel", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + + /// + /// Display Online Start + /// + [JoinName("DisplayOnlineStart")] + public JoinDataComplete DisplayOnlineStart = new JoinDataComplete(new JoinData { JoinNumber = 170, JoinSpan = 10, AttributeName = "Online - Display" }, + new JoinMetadata { Description = "Online - Display", JoinCapabilities = eJoinCapabilities.ToFusion, JoinType = eJoinType.Digital }); + + /// + /// Display 1 Laptop Source Start + /// + [JoinName("Display1LaptopSourceStart")] + public JoinDataComplete Display1LaptopSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 165, JoinSpan = 5, AttributeName = "Display 1 - Source Laptop" }, + new JoinMetadata { Description = "Display 1 - Source Laptop", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + + /// + /// Display 1 Disc Player Source Start + /// + [JoinName("Display1DiscPlayerSourceStart")] + public JoinDataComplete Display1DiscPlayerSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 180, JoinSpan = 5, AttributeName = "Display 1 - Source Disc Player" }, + new JoinMetadata { Description = "Display 1 - Source Disc Player", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + + /// + /// Display 1 Set Top Box Source Start + /// + [JoinName("Display1SetTopBoxSourceStart")] + public JoinDataComplete Display1SetTopBoxSourceStart = new JoinDataComplete(new JoinData { JoinNumber = 185, JoinSpan = 5, AttributeName = "Display 1 - Source TV" }, + new JoinMetadata { Description = "Display 1 - Source TV", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + + // Display 1 + /// + /// Display 1 Start + /// + [JoinName("Display1Start")] + public JoinDataComplete Display1Start = new JoinDataComplete(new JoinData { JoinNumber = 190, JoinSpan = 1 }, + new JoinMetadata { Description = "Display 1 Start", JoinCapabilities = eJoinCapabilities.ToFromFusion, JoinType = eJoinType.Digital }); + + /// + /// Constructor to use when instantiating this Join Map without inheriting from it + /// + /// Join this join map will start at + public EssentialsHuddleSpaceRoomFusionRoomJoinMap(uint joinStart) + : base(joinStart, typeof(EssentialsHuddleSpaceRoomFusionRoomJoinMap)) + { + + } + + /// + /// Constructor to use when extending this Join map + /// + /// Join this join map will start at + /// Type of the child join map + public EssentialsHuddleSpaceRoomFusionRoomJoinMap(uint joinStart, Type type) : base(joinStart, type) + { + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/FusionCustomPropertiesBridge.cs b/src/PepperDash.Essentials.Core/Fusion/FusionCustomPropertiesBridge.cs index f4493fa5..12f5d7e4 100644 --- a/src/PepperDash.Essentials.Core/Fusion/FusionCustomPropertiesBridge.cs +++ b/src/PepperDash.Essentials.Core/Fusion/FusionCustomPropertiesBridge.cs @@ -8,89 +8,129 @@ using PepperDash.Core; using PepperDash.Essentials.Core.Devices; using Serilog.Events; -namespace PepperDash.Essentials.Core.Fusion; - -/// -/// Handles mapping Fusion Custom Property values to system properties -/// -public class FusionCustomPropertiesBridge +namespace PepperDash.Essentials.Core.Fusion { - /// - /// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed + /// Handles mapping Fusion Custom Property values to system properties /// - /// The room associated with this Fusion instance - /// The room information from Fusion - /// - public void EvaluateRoomInfo(IEssentialsRoom room, RoomInformation roomInfo, bool useFusionRoomName) + public class FusionCustomPropertiesBridge { - var reconfigurableDevices = DeviceManager.AllDevices.Where(d => d is ReconfigurableDevice); - foreach (var device in reconfigurableDevices) + /// + /// Evaluates the room info and custom properties from Fusion and updates the system properties aa needed + /// + /// The room associated with this Fusion instance + /// The room information from Fusion + /// + public void EvaluateRoomInfo(IEssentialsRoom room, RoomInformation roomInfo, bool useFusionRoomName) { - // Get the current device config so new values can be overwritten over existing - var deviceConfig = (device as ReconfigurableDevice).Config; - - if (device is IEssentialsRoom) + try { - // Skipping room name as this will affect ALL room instances in the configuration and cause unintended consequences when multiple rooms are present and multiple Fusion instances are used - continue; - } + var reconfigurableDevices = DeviceManager.AllDevices.OfType(); - if (device is RoomOnToDefaultSourceWhenOccupied) + foreach (var device in reconfigurableDevices) + { + // Get the current device config so new values can be overwritten over existing + var deviceConfig = device.Config; + + if (device is IEssentialsRoom) + { + // Skipping room name as this will affect ALL room instances in the configuration and cause unintended consequences when multiple rooms are present and multiple Fusion instances are used + continue; + } + + if (device is RoomOnToDefaultSourceWhenOccupied) + { + Debug.LogMessage(LogEventLevel.Debug, "Mapping Room on via Occupancy values from Fusion"); + + var devProps = JsonConvert.DeserializeObject(deviceConfig.Properties.ToString()); + + var enableFeature = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupied")); + if (enableFeature != null) + devProps.EnableRoomOnWhenOccupied = bool.Parse(enableFeature.CustomFieldValue); + + var enableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedStartTime")); + if (enableTime != null) + devProps.OccupancyStartTime = enableTime.CustomFieldValue; + + var disableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedEndTime")); + if (disableTime != null) + devProps.OccupancyEndTime = disableTime.CustomFieldValue; + + var enableSunday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSun")); + if (enableSunday != null) + devProps.EnableSunday = bool.Parse(enableSunday.CustomFieldValue); + + var enableMonday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedMon")); + if (enableMonday != null) + devProps.EnableMonday = bool.Parse(enableMonday.CustomFieldValue); + + var enableTuesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedTue")); + if (enableTuesday != null) + devProps.EnableTuesday = bool.Parse(enableTuesday.CustomFieldValue); + + var enableWednesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedWed")); + if (enableWednesday != null) + devProps.EnableWednesday = bool.Parse(enableWednesday.CustomFieldValue); + + var enableThursday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedThu")); + if (enableThursday != null) + devProps.EnableThursday = bool.Parse(enableThursday.CustomFieldValue); + + var enableFriday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedFri")); + if (enableFriday != null) + devProps.EnableFriday = bool.Parse(enableFriday.CustomFieldValue); + + var enableSaturday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSat")); + if (enableSaturday != null) + devProps.EnableSaturday = bool.Parse(enableSaturday.CustomFieldValue); + + deviceConfig.Properties = JToken.FromObject(devProps); + } + + // Set the config on the device + device.SetConfig(deviceConfig); + } + + if (!(room is ReconfigurableDevice reconfigurable)) + { + Debug.LogWarning("FusionCustomPropertiesBridge: Room is not a ReconfigurableDevice. Cannot map custom properties."); + return; + } + + var roomConfig = reconfigurable.Config; + + var updateConfig = false; + + // Set the room name + if (!string.IsNullOrEmpty(roomInfo.Name) && useFusionRoomName) + { + Debug.LogDebug("Current Room Name: {currentName}. New Room Name: {fusionName}", roomConfig.Name, roomInfo.Name); + // Set the name in config + roomConfig.Name = roomInfo.Name; + updateConfig = true; + + Debug.LogDebug("Room Name Successfully Changed."); + } + + // Set the help message + var helpMessage = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomHelpMessage")); + if (helpMessage != null) + { + roomConfig.Properties["helpMessage"] = helpMessage.CustomFieldValue; + updateConfig = true; + } + + if (updateConfig) + { + reconfigurable.SetConfig(roomConfig); + } + } + catch (Exception e) { - Debug.LogMessage(LogEventLevel.Debug, "Mapping Room on via Occupancy values from Fusion"); - - var devProps = JsonConvert.DeserializeObject(deviceConfig.Properties.ToString()); - - var enableFeature = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupied")); - if (enableFeature != null) - devProps.EnableRoomOnWhenOccupied = bool.Parse(enableFeature.CustomFieldValue); - - var enableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedStartTime")); - if (enableTime != null) - devProps.OccupancyStartTime = enableTime.CustomFieldValue; - - var disableTime = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("RoomOnWhenOccupiedEndTime")); - if (disableTime != null) - devProps.OccupancyEndTime = disableTime.CustomFieldValue; - - var enableSunday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSun")); - if (enableSunday != null) - devProps.EnableSunday = bool.Parse(enableSunday.CustomFieldValue); - - var enableMonday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedMon")); - if (enableMonday != null) - devProps.EnableMonday = bool.Parse(enableMonday.CustomFieldValue); - - var enableTuesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedTue")); - if (enableTuesday != null) - devProps.EnableTuesday = bool.Parse(enableTuesday.CustomFieldValue); - - var enableWednesday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedWed")); - if (enableWednesday != null) - devProps.EnableWednesday = bool.Parse(enableWednesday.CustomFieldValue); - - var enableThursday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedThu")); - if (enableThursday != null) - devProps.EnableThursday = bool.Parse(enableThursday.CustomFieldValue); - - var enableFriday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedFri")); - if (enableFriday != null) - devProps.EnableFriday = bool.Parse(enableFriday.CustomFieldValue); - - var enableSaturday = roomInfo.FusionCustomProperties.FirstOrDefault(p => p.ID.Equals("EnRoomOnWhenOccupiedSat")); - if (enableSaturday != null) - devProps.EnableSaturday = bool.Parse(enableSaturday.CustomFieldValue); - - deviceConfig.Properties = JToken.FromObject(devProps); + Debug.LogError("FusionCustomPropetiesBridge: Exception mapping properties for {roomKey}: {message}", room.Key, e.Message); + Debug.LogDebug(e, "Stack Trace: "); } - - - // Set the config on the device - (device as ReconfigurableDevice).SetConfig(deviceConfig); } - - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/FusionEventHandlers.cs b/src/PepperDash.Essentials.Core/Fusion/FusionEventHandlers.cs index c6f766ea..6f374237 100644 --- a/src/PepperDash.Essentials.Core/Fusion/FusionEventHandlers.cs +++ b/src/PepperDash.Essentials.Core/Fusion/FusionEventHandlers.cs @@ -1,5 +1,8 @@ using System; - +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; namespace PepperDash.Essentials.Core.Fusion { diff --git a/src/PepperDash.Essentials.Core/Fusion/FusionProcessorQueries.cs b/src/PepperDash.Essentials.Core/Fusion/FusionProcessorQueries.cs index aefe37ac..86ed62f3 100644 --- a/src/PepperDash.Essentials.Core/Fusion/FusionProcessorQueries.cs +++ b/src/PepperDash.Essentials.Core/Fusion/FusionProcessorQueries.cs @@ -1,67 +1,73 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using Crestron.SimplSharp; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Fusion; - -/// -/// When created, runs progcomments on every slot and stores the program names in a list -/// -public class ProcessorProgReg +namespace PepperDash.Essentials.Core.Fusion { /// - /// Gets the processor program registry + /// When created, runs progcomments on every slot and stores the program names in a list /// - /// - public static Dictionary GetProcessorProgReg() + public class ProcessorProgReg { - var programs = new Dictionary(); - for (int i = 1; i <= Global.ControlSystem.NumProgramsSupported; i++) + //public static Dictionary Programs { get; private set; } + + /// + /// Gets the processor program registry + /// + /// + public static Dictionary GetProcessorProgReg() { - string response = null; - var success = CrestronConsole.SendControlSystemCommand("progcomments:" + i, ref response); - var item = new ProcessorProgramItem(); - if (!success) - item.Name = "Error: PROGCOMMENTS failed"; - else + var programs = new Dictionary(); + for (int i = 1; i <= Global.ControlSystem.NumProgramsSupported; i++) { - if (response.ToLower().Contains("bad or incomplete")) - item.Name = ""; + string response = null; + var success = CrestronConsole.SendControlSystemCommand("progcomments:" + i, ref response); + var item = new ProcessorProgramItem(); + if (!success) + item.Name = "Error: PROGCOMMENTS failed"; else { - var startPos = response.IndexOf("Program File"); - var colonPos = response.IndexOf(":", startPos) + 1; - var endPos = response.IndexOf(CrestronEnvironment.NewLine, colonPos); - item.Name = response.Substring(colonPos, endPos - colonPos).Trim(); - item.Exists = true; - if (item.Name.Contains(".dll")) + if (response.ToLower().Contains("bad or incomplete")) + item.Name = ""; + else { - startPos = response.IndexOf("Compiler Revision"); - colonPos = response.IndexOf(":", startPos) + 1; - endPos = response.IndexOf(CrestronEnvironment.NewLine, colonPos); - item.Name = item.Name + "_v" + response.Substring(colonPos, endPos - colonPos).Trim(); + var startPos = response.IndexOf("Program File"); + var colonPos = response.IndexOf(":", startPos) + 1; + var endPos = response.IndexOf(CrestronEnvironment.NewLine, colonPos); + item.Name = response.Substring(colonPos, endPos - colonPos).Trim(); + item.Exists = true; + if (item.Name.Contains(".dll")) + { + startPos = response.IndexOf("Compiler Revision"); + colonPos = response.IndexOf(":", startPos) + 1; + endPos = response.IndexOf(CrestronEnvironment.NewLine, colonPos); + item.Name = item.Name + "_v" + response.Substring(colonPos, endPos - colonPos).Trim(); + } } } + programs[i] = item; + Debug.LogMessage(LogEventLevel.Debug, "Program {0}: {1}", i, item.Name); } - programs[i] = item; - Debug.LogMessage(LogEventLevel.Debug, "Program {0}: {1}", i, item.Name); + return programs; } - return programs; } -} -/// -/// Represents a ProcessorProgramItem -/// -public class ProcessorProgramItem -{ /// - /// Gets or sets the Exists + /// Represents a ProcessorProgramItem /// - public bool Exists { get; set; } - /// - /// Gets or sets the Name - /// - public string Name { get; set; } -} + public class ProcessorProgramItem + { + /// + /// Gets or sets the Exists + /// + public bool Exists { get; set; } + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/FusionRviDataClasses.cs b/src/PepperDash.Essentials.Core/Fusion/FusionRviDataClasses.cs index ba53564a..cef4926f 100644 --- a/src/PepperDash.Essentials.Core/Fusion/FusionRviDataClasses.cs +++ b/src/PepperDash.Essentials.Core/Fusion/FusionRviDataClasses.cs @@ -8,492 +8,828 @@ using Crestron.SimplSharpPro.Fusion; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Fusion; - -// Helper Classes for GUIDs - -/// -/// Stores GUIDs to be written to a file in NVRAM -/// -public class FusionRoomGuids +namespace PepperDash.Essentials.Core.Fusion { - public string RoomName { get; set; } - public uint IpId { get; set; } - public string RoomGuid { get; set; } - public FusionOccupancySensorAsset OccupancyAsset { get; set; } - public Dictionary StaticAssets { get; set; } - - public FusionRoomGuids() - { - StaticAssets = new Dictionary(); - OccupancyAsset = new FusionOccupancySensorAsset(); - } - - public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets) - { - RoomName = roomName; - IpId = ipId; - RoomGuid = roomGuid; - - StaticAssets = staticAssets; - OccupancyAsset = new FusionOccupancySensorAsset(); - } - - public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets, FusionOccupancySensorAsset occAsset) - { - RoomName = roomName; - IpId = ipId; - RoomGuid = roomGuid; - - StaticAssets = staticAssets; - OccupancyAsset = occAsset; - } + // Helper Classes for GUIDs /// - /// Generates a new room GUID prefixed by the program slot number and NIC MAC address + /// Stores GUIDs to be written to a file in NVRAM /// - /// - /// - public string GenerateNewRoomGuid(uint progSlot, string mac) + public class FusionRoomGuids { - Guid roomGuid = Guid.NewGuid(); + /// + /// Gets or sets the RoomName + /// + public string RoomName { get; set; } - return string.Format("{0}-{1}-{2}", progSlot, mac, roomGuid.ToString()); - } + /// + /// Gets or sets the IpId + /// + public uint IpId { get; set; } + /// + /// Gets or sets the RoomGuid + /// + public string RoomGuid { get; set; } - /// - /// Adds an asset to the StaticAssets collection and returns the new asset - /// - /// - /// - /// - /// - /// - /// - public FusionAsset AddStaticAsset(FusionRoom room, int uid, string assetName, string type, string instanceId) - { - var slotNum = GetNextAvailableAssetNumber(room); + /// + /// Gets or sets the OccupancyAsset + /// + public FusionOccupancySensorAsset OccupancyAsset { get; set; } - Debug.LogMessage(LogEventLevel.Verbose, "Adding Fusion Asset: {0} of Type: {1} at Slot Number: {2} with GUID: {3}", assetName, type, slotNum, instanceId); + /// + /// Gets or sets the StaticAssets + /// + public Dictionary StaticAssets { get; set; } - var tempAsset = new FusionAsset(slotNum, assetName, type, instanceId); - - StaticAssets.Add(uid, tempAsset); - - return tempAsset; - } - - /// - /// Returns the next available slot number in the Fusion UserConfigurableAssetDetails collection - /// - /// - /// - public static uint GetNextAvailableAssetNumber(FusionRoom room) - { - uint slotNum = 0; - - foreach (var item in room.UserConfigurableAssetDetails) + /// + /// FusionRoomGuids constructor + /// + public FusionRoomGuids() { - if(item.Number > slotNum) - slotNum = item.Number; + StaticAssets = new Dictionary(); + OccupancyAsset = new FusionOccupancySensorAsset(); } - if (slotNum < 5) + /// + /// FusionRoomGuids constructor + /// + /// name of the fusion room + /// ipID of the fusion room + /// room GUID + /// dictionary of assets + public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets) { - slotNum = 5; + RoomName = roomName; + IpId = ipId; + RoomGuid = roomGuid; + + StaticAssets = staticAssets; + OccupancyAsset = new FusionOccupancySensorAsset(); } - else - slotNum = slotNum + 1; - Debug.LogMessage(LogEventLevel.Verbose, "#Next available fusion asset number is: {0}", slotNum); - - return slotNum; - } - -} - -public class FusionOccupancySensorAsset -{ - // SlotNumber fixed at 4 - - public uint SlotNumber { get { return 4; } } - public string Name { get { return "Occupancy Sensor"; } } - public eAssetType Type { get; set; } - public string InstanceId { get; set; } - - public FusionOccupancySensorAsset() - { - } - - public FusionOccupancySensorAsset(eAssetType type) - { - Type = type; - - InstanceId = Guid.NewGuid().ToString(); - } -} - -public class FusionAsset -{ - public uint SlotNumber { get; set; } - public string Name { get; set; } - public string Type { get; set; } - public string InstanceId { get;set; } - - public FusionAsset() - { - - } - - public FusionAsset(uint slotNum, string assetName, string type, string instanceId) - { - SlotNumber = slotNum; - Name = assetName; - Type = type; - if (string.IsNullOrEmpty(instanceId)) + /// + /// FusionRoomGuids constructor + /// + /// name of the fusion room + /// ipID of the fusion room + /// room GUID + /// dictionary of assets + /// occupancy asset + public FusionRoomGuids(string roomName, uint ipId, string roomGuid, Dictionary staticAssets, FusionOccupancySensorAsset occAsset) { + RoomName = roomName; + IpId = ipId; + RoomGuid = roomGuid; + + StaticAssets = staticAssets; + OccupancyAsset = occAsset; + } + + /// + /// Generates a new room GUID prefixed by the program slot number and NIC MAC address + /// + /// + /// + /// + /// GenerateNewRoomGuid method + /// + public string GenerateNewRoomGuid(uint progSlot, string mac) + { + Guid roomGuid = Guid.NewGuid(); + + return string.Format("{0}-{1}-{2}", progSlot, mac, roomGuid.ToString()); + } + + + /// + /// Adds an asset to the StaticAssets collection and returns the new asset + /// + /// + /// + /// + /// + /// + /// + public FusionAsset AddStaticAsset(FusionRoom room, int uid, string assetName, string type, string instanceId) + { + var slotNum = GetNextAvailableAssetNumber(room); + + Debug.LogMessage(LogEventLevel.Verbose, "Adding Fusion Asset: {0} of Type: {1} at Slot Number: {2} with GUID: {3}", assetName, type, slotNum, instanceId); + + var tempAsset = new FusionAsset(slotNum, assetName, type, instanceId); + + StaticAssets.Add(uid, tempAsset); + + return tempAsset; + } + + /// + /// Returns the next available slot number in the Fusion UserConfigurableAssetDetails collection + /// + /// + /// + /// + /// GetNextAvailableAssetNumber method + /// + public static uint GetNextAvailableAssetNumber(FusionRoom room) + { + uint slotNum = 0; + + foreach (var item in room.UserConfigurableAssetDetails) + { + if(item.Number > slotNum) + slotNum = item.Number; + } + + if (slotNum < 5) + { + slotNum = 5; + } + else + slotNum = slotNum + 1; + + Debug.LogMessage(LogEventLevel.Verbose, "#Next available fusion asset number is: {0}", slotNum); + + return slotNum; + } + + } + + /// + /// Represents a FusionOccupancySensorAsset + /// + public class FusionOccupancySensorAsset + { + // SlotNumber fixed at 4 + + /// + /// Gets or sets the SlotNumber + /// + public uint SlotNumber { get { return 4; } } + /// + /// Gets or sets the Name + /// + public string Name { get { return "Occupancy Sensor"; } } + /// + /// Gets or sets the Type + /// + public eAssetType Type { get; set; } + /// + /// Gets or sets the InstanceId + /// + public string InstanceId { get; set; } + + /// + /// Default constructor + /// + public FusionOccupancySensorAsset() + { + } + + /// + /// FusionOccupancySensorAsset constructor + /// + /// asset type + public FusionOccupancySensorAsset(eAssetType type) + { + Type = type; + InstanceId = Guid.NewGuid().ToString(); } - else - { - InstanceId = instanceId; - } } -} - -//*************************************************************************************************** - -public class RoomSchedule -{ - public List Meetings { get; set; } - - public RoomSchedule() - { - Meetings = new List(); - } -} - -//**************************************************************************************************** -// Helper Classes for XML API - -/// -/// Data needed to request the local time from the Fusion server -/// -public class LocalTimeRequest -{ - public string RequestID { get; set; } -} - -/// -/// All the data needed for a full schedule request in a room -/// -/// //[XmlRoot(ElementName = "RequestSchedule")] -public class RequestSchedule -{ - //[XmlElement(ElementName = "RequestID")] - public string RequestID { get; set; } - //[XmlElement(ElementName = "RoomID")] - public string RoomID { get; set; } - //[XmlElement(ElementName = "Start")] - public DateTime Start { get; set; } - //[XmlElement(ElementName = "HourSpan")] - public double HourSpan { get; set; } - - public RequestSchedule(string requestID, string roomID) - { - RequestID = requestID; - RoomID = roomID; - Start = DateTime.Now; - HourSpan = 24; - } -} - - -//[XmlRoot(ElementName = "RequestAction")] -public class RequestAction -{ - //[XmlElement(ElementName = "RequestID")] - public string RequestID { get; set; } - //[XmlElement(ElementName = "RoomID")] - public string RoomID { get; set; } - //[XmlElement(ElementName = "ActionID")] - public string ActionID { get; set; } - //[XmlElement(ElementName = "Parameters")] - public List Parameters { get; set; } - - public RequestAction(string roomID, string actionID, List parameters) - { - RoomID = roomID; - ActionID = actionID; - Parameters = parameters; - } -} - -//[XmlRoot(ElementName = "ActionResponse")] -public class ActionResponse -{ - //[XmlElement(ElementName = "RequestID")] - public string RequestID { get; set; } - //[XmlElement(ElementName = "ActionID")] - public string ActionID { get; set; } - //[XmlElement(ElementName = "Parameters")] - public List Parameters { get; set; } -} - -//[XmlRoot(ElementName = "Parameter")] -public class Parameter -{ - //[XmlAttribute(AttributeName = "ID")] - public string ID { get; set; } - //[XmlAttribute(AttributeName = "Value")] - public string Value { get; set; } -} - -////[XmlRoot(ElementName = "Parameters")] -//public class Parameters -//{ -// //[XmlElement(ElementName = "Parameter")] -// public List Parameter { get; set; } -//} - -/// -/// Data structure for a ScheduleResponse from Fusion -/// -/// //[XmlRoot(ElementName = "ScheduleResponse")] -public class ScheduleResponse -{ - //[XmlElement(ElementName = "RequestID")] - public string RequestID { get; set; } - //[XmlElement(ElementName = "RoomID")] - public string RoomID { get; set; } - //[XmlElement(ElementName = "RoomName")] - public string RoomName { get; set; } - //[XmlElement("Event")] - public List Events { get; set; } - - public ScheduleResponse() - { - Events = new List(); - } -} - -//[XmlRoot(ElementName = "Event")] -public class Event -{ - //[XmlElement(ElementName = "MeetingID")] - public string MeetingID { get; set; } - //[XmlElement(ElementName = "RVMeetingID")] - public string RVMeetingID { get; set; } - //[XmlElement(ElementName = "Recurring")] - public string Recurring { get; set; } - //[XmlElement(ElementName = "InstanceID")] - public string InstanceID { get; set; } - //[XmlElement(ElementName = "dtStart")] - public DateTime dtStart { get; set; } - //[XmlElement(ElementName = "dtEnd")] - public DateTime dtEnd { get; set; } - //[XmlElement(ElementName = "Organizer")] - public string Organizer { get; set; } - //[XmlElement(ElementName = "Attendees")] - public Attendees Attendees { get; set; } - //[XmlElement(ElementName = "Resources")] - public Resources Resources { get; set; } - //[XmlElement(ElementName = "IsEvent")] - public string IsEvent { get; set; } - //[XmlElement(ElementName = "IsRoomViewMeeting")] - public string IsRoomViewMeeting { get; set; } - //[XmlElement(ElementName = "IsPrivate")] - public string IsPrivate { get; set; } - //[XmlElement(ElementName = "IsExchangePrivate")] - public string IsExchangePrivate { get; set; } - //[XmlElement(ElementName = "MeetingTypes")] - public MeetingTypes MeetingTypes { get; set; } - //[XmlElement(ElementName = "ParticipantCode")] - public string ParticipantCode { get; set; } - //[XmlElement(ElementName = "PhoneNo")] - public string PhoneNo { get; set; } - //[XmlElement(ElementName = "WelcomeMsg")] - public string WelcomeMsg { get; set; } - //[XmlElement(ElementName = "Subject")] - public string Subject { get; set; } - //[XmlElement(ElementName = "LiveMeeting")] - public LiveMeeting LiveMeeting { get; set; } - //[XmlElement(ElementName = "ShareDocPath")] - public string ShareDocPath { get; set; } - //[XmlElement(ElementName = "HaveAttendees")] - public string HaveAttendees { get; set; } - //[XmlElement(ElementName = "HaveResources")] - public string HaveResources { get; set; } /// - /// Gets the duration of the meeting + /// Represents a FusionAsset /// - public string DurationInMinutes + public class FusionAsset { - get - { - string duration; + /// + /// Gets or sets the SlotNumber + /// + public uint SlotNumber { get; set; } + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + /// + /// Gets or sets the Type + /// + public string Type { get; set; } + /// + /// Gets or sets the InstanceId + /// + public string InstanceId { get;set; } - var timeSpan = dtEnd.Subtract(dtStart); - int hours = timeSpan.Hours; - double minutes = timeSpan.Minutes; - double roundedMinutes = Math.Round(minutes); - if (hours > 0) + /// + /// Default constructor + /// + public FusionAsset() + { + + } + + /// + /// FusionAsset constructor + /// + /// slot number of asset + /// name of the asset + /// type of the asset + /// instance ID of the asset + public FusionAsset(uint slotNum, string assetName, string type, string instanceId) + { + SlotNumber = slotNum; + Name = assetName; + Type = type; + if (string.IsNullOrEmpty(instanceId)) { - duration = string.Format("{0} hours {1} minutes", hours, roundedMinutes); + InstanceId = Guid.NewGuid().ToString(); } else { - duration = string.Format("{0} minutes", roundedMinutes); + InstanceId = instanceId; } - - return duration; } } + //*************************************************************************************************** + /// - /// Gets the remaining time in the meeting. Returns null if the meeting is not currently in progress. + /// Represents a RoomSchedule /// - public string RemainingTime + public class RoomSchedule { - get + /// + /// Gets or sets the Meetings + /// + public List Meetings { get; set; } + + /// + /// RoomSchedule constructor + /// + public RoomSchedule() { - var now = DateTime.Now; + Meetings = new List(); + } + } - string remainingTime; + //**************************************************************************************************** + // Helper Classes for XML API - if (GetInProgress()) + /// + /// Represents a LocalTimeRequest + /// + public class LocalTimeRequest + { + /// + /// Gets or sets the RequestID + /// + public string RequestID { get; set; } + } + + /// + /// All the data needed for a full schedule request in a room + /// + /// //[XmlRoot(ElementName = "RequestSchedule")] + public class RequestSchedule + { + //[XmlElement(ElementName = "RequestID")] + /// + /// Gets or sets the RequestID + /// + public string RequestID { get; set; } + + //[XmlElement(ElementName = "RoomID")] + /// + /// Gets or sets the RoomID + /// + public string RoomID { get; set; } + + //[XmlElement(ElementName = "Start")] + /// + /// Gets or sets the Start + /// + public DateTime Start { get; set; } + + //[XmlElement(ElementName = "HourSpan")] + /// + /// Gets or sets the HourSpan + /// + public double HourSpan { get; set; } + + /// + /// RequestSchedule constructor + /// + /// id of the request + /// id of the room + public RequestSchedule(string requestID, string roomID) + { + RequestID = requestID; + RoomID = roomID; + Start = DateTime.Now; + HourSpan = 24; + } + } + + + //[XmlRoot(ElementName = "RequestAction")] + /// + /// Represents a RequestAction + /// + public class RequestAction + { + //[XmlElement(ElementName = "RequestID")] + /// + /// Gets or sets the RequestID + /// + public string RequestID { get; set; } + //[XmlElement(ElementName = "RoomID")] + /// + /// Gets or sets the RoomID + /// + public string RoomID { get; set; } + //[XmlElement(ElementName = "ActionID")] + /// + /// Gets or sets the ActionID + /// + public string ActionID { get; set; } + //[XmlElement(ElementName = "Parameters")] + /// + /// Gets or sets the Parameters + /// + public List Parameters { get; set; } + + /// + /// RequestAction constructor + /// + /// id of the room + /// id of the action + /// list of parameters + public RequestAction(string roomID, string actionID, List parameters) + { + RoomID = roomID; + ActionID = actionID; + Parameters = parameters; + } + } + + //[XmlRoot(ElementName = "ActionResponse")] + /// + /// Represents a ActionResponse + /// + public class ActionResponse + { + //[XmlElement(ElementName = "RequestID")] + /// + /// Gets or sets the RequestID + /// + public string RequestID { get; set; } + + //[XmlElement(ElementName = "ActionID")] + /// + /// Gets or sets the ActionID + /// + public string ActionID { get; set; } + + //[XmlElement(ElementName = "Parameters")] + /// + /// Gets or sets the Parameters + /// + public List Parameters { get; set; } + } + + //[XmlRoot(ElementName = "Parameter")] + /// + /// Represents a Parameter + /// + public class Parameter + { + //[XmlAttribute(AttributeName = "ID")] + /// + /// Gets or sets the ID + /// + public string ID { get; set; } + + //[XmlAttribute(AttributeName = "Value")] + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } + + ////[XmlRoot(ElementName = "Parameters")] + //public class Parameters + //{ + // //[XmlElement(ElementName = "Parameter")] + // public List Parameter { get; set; } + //} + + /// + /// Data structure for a ScheduleResponse from Fusion + /// + /// //[XmlRoot(ElementName = "ScheduleResponse")] + public class ScheduleResponse + { + //[XmlElement(ElementName = "RequestID")] + /// + /// Gets or sets the RequestID + /// + public string RequestID { get; set; } + + //[XmlElement(ElementName = "RoomID")] + /// + /// Gets or sets the RoomID + /// + public string RoomID { get; set; } + + //[XmlElement(ElementName = "RoomName")] + /// + /// Gets or sets the RoomName + /// + public string RoomName { get; set; } + + //[XmlElement("Event")] + /// + /// Gets or sets the Events + /// + public List Events { get; set; } + + /// + /// ScheduleResponse constructor + /// + public ScheduleResponse() + { + Events = new List(); + } + } + + //[XmlRoot(ElementName = "Event")] + /// + /// Represents a Event + /// + public class Event + { + //[XmlElement(ElementName = "MeetingID")] + /// + /// Gets or sets the MeetingID + /// + public string MeetingID { get; set; } + //[XmlElement(ElementName = "RVMeetingID")] + /// + /// Gets or sets the RVMeetingID + /// + public string RVMeetingID { get; set; } + //[XmlElement(ElementName = "Recurring")] + /// + /// Gets or sets the Recurring + /// + public string Recurring { get; set; } + //[XmlElement(ElementName = "InstanceID")] + /// + /// Gets or sets the InstanceID + /// + public string InstanceID { get; set; } + //[XmlElement(ElementName = "dtStart")] + /// + /// Gets or sets the dtStart + /// + public DateTime dtStart { get; set; } + //[XmlElement(ElementName = "dtEnd")] + /// + /// Gets or sets the dtEnd + /// + public DateTime dtEnd { get; set; } + //[XmlElement(ElementName = "Organizer")] + /// + /// Gets or sets the Organizer + /// + public string Organizer { get; set; } + //[XmlElement(ElementName = "Attendees")] + /// + /// Gets or sets the Attendees + /// + public Attendees Attendees { get; set; } + //[XmlElement(ElementName = "Resources")] + /// + /// Gets or sets the Resources + /// + public Resources Resources { get; set; } + //[XmlElement(ElementName = "IsEvent")] + /// + /// Gets or sets the IsEvent + /// + public string IsEvent { get; set; } + //[XmlElement(ElementName = "IsRoomViewMeeting")] + /// + /// Gets or sets the IsRoomViewMeeting + /// + public string IsRoomViewMeeting { get; set; } + //[XmlElement(ElementName = "IsPrivate")] + /// + /// Gets or sets the IsPrivate + /// + public string IsPrivate { get; set; } + //[XmlElement(ElementName = "IsExchangePrivate")] + /// + /// Gets or sets the IsExchangePrivate + /// + public string IsExchangePrivate { get; set; } + //[XmlElement(ElementName = "MeetingTypes")] + /// + /// Gets or sets the MeetingTypes + /// + public MeetingTypes MeetingTypes { get; set; } + //[XmlElement(ElementName = "ParticipantCode")] + /// + /// Gets or sets the ParticipantCode + /// + public string ParticipantCode { get; set; } + //[XmlElement(ElementName = "PhoneNo")] + /// + /// Gets or sets the PhoneNo + /// + public string PhoneNo { get; set; } + //[XmlElement(ElementName = "WelcomeMsg")] + /// + /// Gets or sets the WelcomeMsg + /// + public string WelcomeMsg { get; set; } + //[XmlElement(ElementName = "Subject")] + /// + /// Gets or sets the Subject + /// + public string Subject { get; set; } + //[XmlElement(ElementName = "LiveMeeting")] + /// + /// Gets or sets the LiveMeeting + /// + public LiveMeeting LiveMeeting { get; set; } + //[XmlElement(ElementName = "ShareDocPath")] + /// + /// Gets or sets the ShareDocPath + /// + public string ShareDocPath { get; set; } + //[XmlElement(ElementName = "HaveAttendees")] + /// + /// Gets or sets the HaveAttendees + /// + public string HaveAttendees { get; set; } + //[XmlElement(ElementName = "HaveResources")] + /// + /// Gets or sets the HaveResources + /// + public string HaveResources { get; set; } + + /// + /// Gets the duration of the meeting + /// + public string DurationInMinutes + { + get { - var timeSpan = dtEnd.Subtract(now); + string duration; + + var timeSpan = dtEnd.Subtract(dtStart); int hours = timeSpan.Hours; double minutes = timeSpan.Minutes; double roundedMinutes = Math.Round(minutes); if (hours > 0) { - remainingTime = string.Format("{0} hours {1} minutes", hours, roundedMinutes); + duration = string.Format("{0} hours {1} minutes", hours, roundedMinutes); } else { - remainingTime = string.Format("{0} minutes", roundedMinutes); + duration = string.Format("{0} minutes", roundedMinutes); } - return remainingTime; + return duration; + } + } + + /// + /// Gets the remaining time in the meeting. Returns null if the meeting is not currently in progress. + /// + public string RemainingTime + { + get + { + var now = DateTime.Now; + + string remainingTime; + + if (GetInProgress()) + { + var timeSpan = dtEnd.Subtract(now); + int hours = timeSpan.Hours; + double minutes = timeSpan.Minutes; + double roundedMinutes = Math.Round(minutes); + if (hours > 0) + { + remainingTime = string.Format("{0} hours {1} minutes", hours, roundedMinutes); + } + else + { + remainingTime = string.Format("{0} minutes", roundedMinutes); + } + + return remainingTime; + } + else + return null; + } + + } + + /// + /// Indicates that the meeting is in progress + /// + public bool isInProgress + { + get + { + return GetInProgress(); + } + } + + /// + /// Determines if the meeting is in progress + /// + /// Returns true if in progress + bool GetInProgress() + { + var now = DateTime.Now; + + if (now > dtStart && now < dtEnd) + { + return true; } else - return null; + return false; } - } + //[XmlRoot(ElementName = "Resources")] /// - /// Indicates that the meeting is in progress + /// Represents a Resources /// - public bool isInProgress + public class Resources { - get - { - return GetInProgress(); - } + //[XmlElement(ElementName = "Rooms")] + /// + /// Gets or sets the Rooms + /// + public Rooms Rooms { get; set; } } + //[XmlRoot(ElementName = "Rooms")] /// - /// Determines if the meeting is in progress + /// Represents a Rooms /// - /// Returns true if in progress - bool GetInProgress() + public class Rooms { - var now = DateTime.Now; - - if (now > dtStart && now < dtEnd) - { - return true; - } - else - return false; + //[XmlElement(ElementName = "Room")] + /// + /// Gets or sets the Room + /// + public List Room { get; set; } } -} -//[XmlRoot(ElementName = "Resources")] -public class Resources -{ - //[XmlElement(ElementName = "Rooms")] - public Rooms Rooms { get; set; } -} + //[XmlRoot(ElementName = "Room")] + /// + /// Represents a Room + /// + public class Room + { + //[XmlElement(ElementName = "Name")] + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + //[XmlElement(ElementName = "ID")] + /// + /// Gets or sets the ID + /// + public string ID { get; set; } + //[XmlElement(ElementName = "MPType")] + /// + /// Gets or sets the MPType + /// + public string MPType { get; set; } + } -//[XmlRoot(ElementName = "Rooms")] -public class Rooms -{ - //[XmlElement(ElementName = "Room")] - public List Room { get; set; } -} + //[XmlRoot(ElementName = "Attendees")] + /// + /// Represents a Attendees + /// + public class Attendees + { + //[XmlElement(ElementName = "Required")] + /// + /// Gets or sets the Required + /// + public Required Required { get; set; } + //[XmlElement(ElementName = "Optional")] + /// + /// Gets or sets the Optional + /// + public Optional Optional { get; set; } + } -//[XmlRoot(ElementName = "Room")] -public class Room -{ - //[XmlElement(ElementName = "Name")] - public string Name { get; set; } - //[XmlElement(ElementName = "ID")] - public string ID { get; set; } - //[XmlElement(ElementName = "MPType")] - public string MPType { get; set; } -} + //[XmlRoot(ElementName = "Required")] + /// + /// Represents a Required + /// + public class Required + { + //[XmlElement(ElementName = "Attendee")] + /// + /// Gets or sets the Attendee + /// + public List Attendee { get; set; } + } -//[XmlRoot(ElementName = "Attendees")] -public class Attendees -{ - //[XmlElement(ElementName = "Required")] - public Required Required { get; set; } - //[XmlElement(ElementName = "Optional")] - public Optional Optional { get; set; } -} + //[XmlRoot(ElementName = "Optional")] + /// + /// Represents a Optional + /// + public class Optional + { + //[XmlElement(ElementName = "Attendee")] + /// + /// Gets or sets the Attendee + /// + public List Attendee { get; set; } + } -//[XmlRoot(ElementName = "Required")] -public class Required -{ - //[XmlElement(ElementName = "Attendee")] - public List Attendee { get; set; } -} + //[XmlRoot(ElementName = "MeetingType")] + /// + /// Represents a MeetingType + /// + public class MeetingType + { + //[XmlAttribute(AttributeName = "ID")] + /// + /// Gets or sets the ID + /// + public string ID { get; set; } + //[XmlAttribute(AttributeName = "Value")] + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } -//[XmlRoot(ElementName = "Optional")] -public class Optional -{ - //[XmlElement(ElementName = "Attendee")] - public List Attendee { get; set; } -} + //[XmlRoot(ElementName = "MeetingTypes")] + /// + /// Represents a MeetingTypes + /// + public class MeetingTypes + { + //[XmlElement(ElementName = "MeetingType")] + /// + /// Gets or sets the MeetingType + /// + public List MeetingType { get; set; } + } -//[XmlRoot(ElementName = "MeetingType")] -public class MeetingType -{ - //[XmlAttribute(AttributeName = "ID")] - public string ID { get; set; } - //[XmlAttribute(AttributeName = "Value")] - public string Value { get; set; } -} + //[XmlRoot(ElementName = "LiveMeeting")] + /// + /// Represents a LiveMeeting + /// + public class LiveMeeting + { + //[XmlElement(ElementName = "URL")] + /// + /// Gets or sets the URL + /// + public string URL { get; set; } + //[XmlElement(ElementName = "ID")] + /// + /// Gets or sets the ID + /// + public string ID { get; set; } + //[XmlElement(ElementName = "Key")] + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + //[XmlElement(ElementName = "Subject")] + /// + /// Gets or sets the Subject + /// + public string Subject { get; set; } + } -//[XmlRoot(ElementName = "MeetingTypes")] -public class MeetingTypes -{ - //[XmlElement(ElementName = "MeetingType")] - public List MeetingType { get; set; } -} - -//[XmlRoot(ElementName = "LiveMeeting")] -public class LiveMeeting -{ - //[XmlElement(ElementName = "URL")] - public string URL { get; set; } - //[XmlElement(ElementName = "ID")] - public string ID { get; set; } - //[XmlElement(ElementName = "Key")] - public string Key { get; set; } - //[XmlElement(ElementName = "Subject")] - public string Subject { get; set; } -} - -//[XmlRoot(ElementName = "LiveMeetingURL")] -public class LiveMeetingURL -{ - //[XmlElement(ElementName = "LiveMeeting")] - public LiveMeeting LiveMeeting { get; set; } + //[XmlRoot(ElementName = "LiveMeetingURL")] + /// + /// Represents a LiveMeetingURL + /// + public class LiveMeetingURL + { + //[XmlElement(ElementName = "LiveMeeting")] + /// + /// Gets or sets the LiveMeeting + /// + public LiveMeeting LiveMeeting { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs index 16631a75..9364a58d 100644 --- a/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs +++ b/src/PepperDash.Essentials.Core/Fusion/IEssentialsRoomFusionController.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Timers; -using Timer = System.Timers.Timer; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronXml; @@ -63,7 +62,7 @@ namespace PepperDash.Essentials.Core.Fusion private Event _currentMeeting; private RoomSchedule _currentSchedule; - private Timer _dailyTimeRequestTimer; + private CTimer _dailyTimeRequestTimer; private StatusMonitorCollection _errorMessageRollUp; private FusionRoomGuids _guids; @@ -71,16 +70,14 @@ namespace PepperDash.Essentials.Core.Fusion private bool _isRegisteredForSchedulePushNotifications; private Event _nextMeeting; - private Timer _pollTimer; + private CTimer _pollTimer; - private Timer _pushNotificationTimer; + private CTimer _pushNotificationTimer; private string _roomOccupancyRemoteString; private bool _helpRequestSent; - private readonly object _guidFileLock = new(); - private eFusionHelpResponse _helpRequestStatus; /// @@ -309,7 +306,7 @@ namespace PepperDash.Essentials.Core.Fusion } /// - protected override void Initialize() + public override void Initialize() { GenerateGuidFile(GetGuidFilePath(_config.IpIdInt)); @@ -393,31 +390,44 @@ namespace PepperDash.Essentials.Core.Fusion return; } - lock (_guidFileLock) + var fileLock = new CCriticalSection(); + + try { - try + if (fileLock.Disposed) { - Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file"); - - _guids = FusionOccSensor == null - ? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets) - : new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor); - - var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented); - - using (var sw = new StreamWriter(filePath)) - { - sw.Write(json); - sw.Flush(); - } - - Debug.LogMessage(LogEventLevel.Debug, this, "Guids successfully written to file '{0}'", filePath); + return; } - catch (Exception e) + + fileLock.Enter(); + + Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file"); + + _guids = FusionOccSensor == null + ? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets) + : new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor); + + var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented); + + using (var sw = new StreamWriter(filePath)) { - Debug.LogMessage(LogEventLevel.Information, this, "Error writing guid file: {0}", e); + sw.Write(json); + sw.Flush(); } - } // end lock + + Debug.LogMessage(LogEventLevel.Debug, this, "Guids successfully written to file '{0}'", filePath); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, this, "Error writing guid file: {0}", e); + } + finally + { + if (!fileLock.Disposed) + { + fileLock.Leave(); + } + } } /// @@ -432,37 +442,50 @@ namespace PepperDash.Essentials.Core.Fusion return; } - lock (_guidFileLock) + var fileLock = new CCriticalSection(); + + try { - try + if (fileLock.Disposed) { - if (File.Exists(filePath)) - { - var json = File.ReadToEnd(filePath, Encoding.ASCII); - - _guids = JsonConvert.DeserializeObject(json); - - // _config.IpId = _guids.IpId; - - FusionStaticAssets = _guids.StaticAssets; - } - - Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}", - filePath); - - Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid); - - foreach (var item in FusionStaticAssets) - { - Debug.LogMessage(LogEventLevel.Debug, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name, - item.Value.SlotNumber, item.Value.InstanceId); - } + return; } - catch (Exception e) + + fileLock.Enter(); + + if (File.Exists(filePath)) { - Debug.LogMessage(LogEventLevel.Information, this, "Error reading guid file: {0}", e); + var json = File.ReadToEnd(filePath, Encoding.ASCII); + + _guids = JsonConvert.DeserializeObject(json); + + // _config.IpId = _guids.IpId; + + FusionStaticAssets = _guids.StaticAssets; } - } // end lock + + Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}", + filePath); + + Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid); + + foreach (var item in FusionStaticAssets) + { + Debug.LogMessage(LogEventLevel.Debug, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name, + item.Value.SlotNumber, item.Value.InstanceId); + } + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, this, "Error reading guid file: {0}", e); + } + finally + { + if (!fileLock.Disposed) + { + fileLock.Leave(); + } + } } /// @@ -501,6 +524,12 @@ namespace PepperDash.Essentials.Core.Fusion // Moved to CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(JoinMap.Display1CurrentSourceName.JoinNumber, JoinMap.Display1CurrentSourceName.AttributeName, eSigIoMask.InputSigOnly); + // Don't think we need to get current status of this as nothing should be alive yet. + if (Room is IHasCurrentSourceInfoChange hasCurrentSourceInfoChange) + { + hasCurrentSourceInfoChange.CurrentSourceChange += Room_CurrentSourceInfoChange; + } + FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction(Room.PowerOnToDefaultOrLastSource); FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() => @@ -724,15 +753,15 @@ namespace PepperDash.Essentials.Core.Fusion RequestLocalDateTime(null); // Setup timer to request time daily - if (_dailyTimeRequestTimer != null) + if (_dailyTimeRequestTimer != null && !_dailyTimeRequestTimer.Disposed) { _dailyTimeRequestTimer.Stop(); _dailyTimeRequestTimer.Dispose(); } - _dailyTimeRequestTimer = new Timer(86400000) { AutoReset = true }; - _dailyTimeRequestTimer.Elapsed += (s, e) => RequestLocalDateTime(null); - _dailyTimeRequestTimer.Start(); + _dailyTimeRequestTimer = new CTimer(RequestLocalDateTime, null, 86400000, 86400000); + + _dailyTimeRequestTimer.Reset(86400000, 86400000); }); } @@ -945,25 +974,25 @@ namespace PepperDash.Essentials.Core.Fusion { case 1: _isRegisteredForSchedulePushNotifications = true; - if (_pollTimer != null) + if (_pollTimer != null && !_pollTimer.Disposed) { _pollTimer.Stop(); _pollTimer.Dispose(); } - _pushNotificationTimer = new Timer(PushNotificationTimeout) { AutoReset = true }; - _pushNotificationTimer.Elapsed += (s, e) => RequestFullRoomSchedule(null); - _pushNotificationTimer.Start(); + _pushNotificationTimer = new CTimer(RequestFullRoomSchedule, null, + PushNotificationTimeout, PushNotificationTimeout); + _pushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); break; case 0: _isRegisteredForSchedulePushNotifications = false; - if (_pushNotificationTimer != null) + if (_pushNotificationTimer != null && !_pushNotificationTimer.Disposed) { _pushNotificationTimer.Stop(); _pushNotificationTimer.Dispose(); } - _pollTimer = new Timer(SchedulePollInterval) { AutoReset = true }; - _pollTimer.Elapsed += (s, e) => RequestFullRoomSchedule(null); - _pollTimer.Start(); + _pollTimer = new CTimer(RequestFullRoomSchedule, null, SchedulePollInterval, + SchedulePollInterval); + _pollTimer.Reset(SchedulePollInterval, SchedulePollInterval); break; } } @@ -1116,10 +1145,7 @@ namespace PepperDash.Essentials.Core.Fusion if (action.OuterXml.IndexOf("RequestSchedule", StringComparison.Ordinal) > -1) { - _pushNotificationTimer.Stop(); - _pushNotificationTimer.Interval = PushNotificationTimeout; - _pushNotificationTimer.Start(); - Debug.LogMessage(LogEventLevel.Verbose, this, "Received push notification for schedule change"); + _pushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); } } else // Not a push notification @@ -1175,9 +1201,7 @@ namespace PepperDash.Essentials.Core.Fusion if (!_isRegisteredForSchedulePushNotifications) { - _pollTimer.Stop(); - _pollTimer.Interval = SchedulePollInterval; - _pollTimer.Start(); + _pollTimer.Reset(SchedulePollInterval, SchedulePollInterval); } // Fire Schedule Change Event @@ -1236,14 +1260,11 @@ namespace PepperDash.Essentials.Core.Fusion uint i = 0; foreach (var kvp in setTopBoxes) { - if (kvp.Value.SourceDevice is Device device) + TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots { - TryAddRouteActionSigs(JoinMap.Display1SetTopBoxSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1SetTopBoxSourceStart.JoinNumber + i, kvp.Key, device); - i++; - if (i > JoinMap.Display1SetTopBoxSourceStart.JoinSpan) // We only have five spots - { - break; - } + break; } } @@ -1251,14 +1272,11 @@ namespace PepperDash.Essentials.Core.Fusion i = 0; foreach (var kvp in discPlayers) { - if (kvp.Value.SourceDevice is Device device) + TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots { - TryAddRouteActionSigs(JoinMap.Display1DiscPlayerSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1DiscPlayerSourceStart.JoinNumber + i, kvp.Key, device); - i++; - if (i > JoinMap.Display1DiscPlayerSourceStart.JoinSpan) // We only have five spots - { - break; - } + break; } } @@ -1266,14 +1284,11 @@ namespace PepperDash.Essentials.Core.Fusion i = 0; foreach (var kvp in laptops) { - if (kvp.Value.SourceDevice is Device device) + TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, kvp.Value.SourceDevice); + i++; + if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots??? { - TryAddRouteActionSigs(JoinMap.Display1LaptopSourceStart.AttributeName + " " + (i + 1), JoinMap.Display1LaptopSourceStart.JoinNumber + i, kvp.Key, device); - i++; - if (i > JoinMap.Display1LaptopSourceStart.JoinSpan) // We only have ten spots??? - { - break; - } + break; } } @@ -1743,6 +1758,36 @@ namespace PepperDash.Essentials.Core.Fusion return Convert.ToInt32(capture.Groups[1].Value); } + /// + /// Event handler for when room source changes + /// + protected void Room_CurrentSourceInfoChange(SourceListItem info, ChangeType type) + { + // Handle null. Nothing to do when switching from or to null + if (info == null || info.SourceDevice == null) + { + return; + } + + var dev = info.SourceDevice; + if (type == ChangeType.WillChange) + { + if (_sourceToFeedbackSigs.ContainsKey(dev)) + { + _sourceToFeedbackSigs[dev].BoolValue = false; + } + } + else + { + if (_sourceToFeedbackSigs.ContainsKey(dev)) + { + _sourceToFeedbackSigs[dev].BoolValue = true; + } + //var name = (room == null ? "" : room.Name); + CurrentRoomSourceNameSig.InputSig.StringValue = info.SourceDevice.Name; + } + } + /// /// Event handler for Fusion state changes /// @@ -1905,13 +1950,12 @@ namespace PepperDash.Essentials.Core.Fusion HelpRequestStatusFeedback.FireUpdate(); } - private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e) + private void OnTimedEvent(object source, ElapsedEventArgs e) { this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name); CancelHelpRequest(); } - /// public void CancelHelpRequest() { diff --git a/src/PepperDash.Essentials.Core/Global/EthernetAdapterInfo.cs b/src/PepperDash.Essentials.Core/Global/EthernetAdapterInfo.cs index 68701639..6565c6de 100644 --- a/src/PepperDash.Essentials.Core/Global/EthernetAdapterInfo.cs +++ b/src/PepperDash.Essentials.Core/Global/EthernetAdapterInfo.cs @@ -4,56 +4,56 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - - -/// -/// Represents a EthernetAdapterInfo -/// -public class EthernetAdapterInfo +namespace PepperDash.Essentials.Core { /// - /// Gets or sets the Type + /// Represents a EthernetAdapterInfo /// - public EthernetAdapterType Type { get; set; } - /// - /// Gets or sets the DhcpIsOn - /// - public bool DhcpIsOn { get; set; } - /// - /// Gets or sets the Hostname - /// - public string Hostname { get; set; } - /// - /// Gets or sets the MacAddress - /// - public string MacAddress { get; set; } - /// - /// Gets or sets the IpAddress - /// - public string IpAddress { get; set; } - /// - /// Gets or sets the Subnet - /// - public string Subnet { get; set; } - /// - /// Gets or sets the Gateway - /// - public string Gateway { get; set; } - /// - /// Gets or sets the Dns1 - /// - public string Dns1 { get; set; } - /// - /// Gets or sets the Dns2 - /// - public string Dns2 { get; set; } - /// - /// Gets or sets the Dns3 - /// - public string Dns3 { get; set; } - /// - /// Gets or sets the Domain - /// - public string Domain { get; set; } -} + public class EthernetAdapterInfo + { + /// + /// Gets or sets the Type + /// + public EthernetAdapterType Type { get; set; } + /// + /// Gets or sets the DhcpIsOn + /// + public bool DhcpIsOn { get; set; } + /// + /// Gets or sets the Hostname + /// + public string Hostname { get; set; } + /// + /// Gets or sets the MacAddress + /// + public string MacAddress { get; set; } + /// + /// Gets or sets the IpAddress + /// + public string IpAddress { get; set; } + /// + /// Gets or sets the Subnet + /// + public string Subnet { get; set; } + /// + /// Gets or sets the Gateway + /// + public string Gateway { get; set; } + /// + /// Gets or sets the Dns1 + /// + public string Dns1 { get; set; } + /// + /// Gets or sets the Dns2 + /// + public string Dns2 { get; set; } + /// + /// Gets or sets the Dns3 + /// + public string Dns3 { get; set; } + /// + /// Gets or sets the Domain + /// + public string Domain { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Global/Global.cs b/src/PepperDash.Essentials.Core/Global/Global.cs index 5eeb5e96..37d4472d 100644 --- a/src/PepperDash.Essentials.Core/Global/Global.cs +++ b/src/PepperDash.Essentials.Core/Global/Global.cs @@ -1,9 +1,12 @@ -using System; + + +using System; using System.Linq; using System.Text.RegularExpressions; using System.Globalization; using Crestron.SimplSharp; using System.Collections.Generic; +using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DM; @@ -11,6 +14,9 @@ using Crestron.SimplSharpPro.DM; using PepperDash.Core; using PepperDash.Essentials.License; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; using Serilog.Events; @@ -48,131 +54,130 @@ namespace PepperDash.Essentials.Core // TODO: consider making this configurable later /// - /// Gets or sets the CultureInfo for the running system. Default is InvariantCulture. This is used for all parsing and formatting in Essentials to ensure consistent behavior regardless of the culture settings of the processor. + /// The CultureInfo for formatting /// - public static IFormatProvider Culture = CultureInfo.InvariantCulture; + public static IFormatProvider Culture = CultureInfo.CreateSpecificCulture("en-US"); - - /// - /// True when the processor type is a DMPS variant - /// - public static bool ControlSystemIsDmpsType - { - get + /// + /// True when the processor type is a DMPS variant + /// + public static bool ControlSystemIsDmpsType { - if(ControlSystem.SystemControl != null) + get { - if(ControlSystem.SystemControl.SystemControlType > 0) + if(ControlSystem.SystemControl != null) { - return true; - } + if(ControlSystem.SystemControl.SystemControlType > 0) + { + return true; + } + } + return false; } - return false; } - } - /// - /// True when the processor type is a DMPS 4K variant - /// - public static bool ControlSystemIsDmps4kType - { - get + /// + /// True when the processor type is a DMPS 4K variant + /// + public static bool ControlSystemIsDmps4kType { - if(ControlSystem.SystemControl != null) + get { - if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K150CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + if(ControlSystem.SystemControl != null) { - return true; - } + if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K150CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + { + return true; + } + } + return false; } - return false; } - } - /// - /// True when the processor type is a DMPS 4K 200/300/250/350 variant - /// - public static bool ControlSystemIsDmps4k3xxType - { - get + /// + /// True when the processor type is a DMPS 4K 200/300/250/350 variant + /// + public static bool ControlSystemIsDmps4k3xxType { - if(ControlSystem.SystemControl != null) + get { - if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || - ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + if(ControlSystem.SystemControl != null) { - return true; - } + if(ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K200CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K250CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K300CSystemControl || + ControlSystem.SystemControl.SystemControlType == eSystemControlType.Dmps34K350CSystemControl) + { + return true; + } + } + return false; } - return false; } - } /// /// Gets or sets the FilePathPrefix /// public static string FilePathPrefix { get; private set; } - /// - /// The file path prefix to the applciation directory - /// - public static string ApplicationDirectoryPathPrefix - { - get + /// + /// The file path prefix to the applciation directory + /// + public static string ApplicationDirectoryPathPrefix { - return Crestron.SimplSharp.CrestronIO.Directory.GetApplicationDirectory(); + get + { + return Crestron.SimplSharp.CrestronIO.Directory.GetApplicationDirectory(); + } } - } - /// - /// Returns the directory separator character based on the running OS - /// - public static char DirectorySeparator - { - get + /// + /// Returns the directory separator character based on the running OS + /// + public static char DirectorySeparator { - return System.IO.Path.DirectorySeparatorChar; + get + { + return System.IO.Path.DirectorySeparatorChar; + } } - } - /// - /// Wildcarded config file name for global reference - /// - public const string ConfigFileName = "*configurationFile*.json"; + /// + /// Wildcarded config file name for global reference + /// + public const string ConfigFileName = "*configurationFile*.json"; - /// - /// Sets the file path prefix - /// - /// - public static void SetFilePathPrefix(string prefix) - { - FilePathPrefix = prefix; - Debug.LogMessage(LogEventLevel.Information, "File Path Prefix set to '{0}'", FilePathPrefix); - } - - static string _AssemblyVersion; - - /// - /// Gets the Assembly Version of Essentials - /// - /// The Assembly Version at Runtime - public static string AssemblyVersion - { - get + /// + /// Sets the file path prefix + /// + /// + public static void SetFilePathPrefix(string prefix) { - return _AssemblyVersion; + FilePathPrefix = prefix; + Debug.LogMessage(LogEventLevel.Information, "File Path Prefix set to '{0}'", FilePathPrefix); } - private set + + static string _AssemblyVersion; + + /// + /// Gets the Assembly Version of Essentials + /// + /// The Assembly Version at Runtime + public static string AssemblyVersion { - _AssemblyVersion = value; + get + { + return _AssemblyVersion; + } + private set + { + _AssemblyVersion = value; + } } - } /// /// Sets the Assembly version to the version of the Essentials Library @@ -197,18 +202,18 @@ namespace PepperDash.Essentials.Core { if (Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*).*").Groups[1].Value == "0") { - Debug.LogMessage(LogEventLevel.Verbose, "Running Local Build. Bypassing Dependency Check."); - return true; + Debug.LogMessage(LogEventLevel.Verbose, "Running Local Build. Bypassing Dependency Check."); + return true; } if (developmentVersions == null) { Debug.LogMessage(LogEventLevel.Information, - "Development Plugin does not specify a list of versions. Loading plugin may not work as expected. Checking Minumum version"); - return IsRunningMinimumVersionOrHigher(minimumVersion); + "Development Plugin does not specify a list of versions. Loading plugin may not work as expected. Checking Minumum version"); + return IsRunningMinimumVersionOrHigher(minimumVersion); } - Debug.LogMessage(LogEventLevel.Verbose, "Comparing running version '{0}' to minimum versions '{1}'", AssemblyVersion, developmentVersions); + Debug.LogMessage(LogEventLevel.Verbose, "Comparing running version '{0}' to minimum versions '{1}'", AssemblyVersion, developmentVersions); var versionMatch = developmentVersions.FirstOrDefault(x => x == AssemblyVersion); @@ -218,84 +223,84 @@ namespace PepperDash.Essentials.Core return false; } - Debug.LogMessage(LogEventLevel.Verbose, "Essentials Build {0} matches list of development builds", AssemblyVersion); + Debug.LogMessage(LogEventLevel.Verbose, "Essentials Build {0} matches list of development builds", AssemblyVersion); return IsRunningMinimumVersionOrHigher(minimumVersion); } - /// - /// Checks to see if the running version meets or exceed the minimum specified version. For beta versions (0.xx.yy), will always return true. - /// - /// Minimum specified version in format of xx.yy.zz - /// Returns true if the running version meets or exceeds the minimum specified version - public static bool IsRunningMinimumVersionOrHigher(string minimumVersion) - { - Debug.LogMessage(LogEventLevel.Verbose, "Comparing running version '{0}' to minimum version '{1}'", AssemblyVersion, minimumVersion); - - if (String.IsNullOrEmpty(minimumVersion)) + /// + /// Checks to see if the running version meets or exceed the minimum specified version. For beta versions (0.xx.yy), will always return true. + /// + /// Minimum specified version in format of xx.yy.zz + /// Returns true if the running version meets or exceeds the minimum specified version + public static bool IsRunningMinimumVersionOrHigher(string minimumVersion) { - Debug.LogMessage(LogEventLevel.Information,"Plugin does not specify a minimum version. Loading plugin may not work as expected. Proceeding with loading plugin"); - return true; - } - - var runtimeVersion = Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*).*"); + Debug.LogMessage(LogEventLevel.Verbose, "Comparing running version '{0}' to minimum version '{1}'", AssemblyVersion, minimumVersion); - var runtimeVersionMajor = Int16.Parse(runtimeVersion.Groups[1].Value); - var runtimeVersionMinor = Int16.Parse(runtimeVersion.Groups[2].Value); - var runtimeVersionBuild = Int16.Parse(runtimeVersion.Groups[3].Value); - - var runtimeVer = new Version(runtimeVersionMajor, runtimeVersionMinor, runtimeVersionBuild); - - Version minimumVer; - try - { - minimumVer = new Version(minimumVersion); - } - catch - { - Debug.LogMessage(LogEventLevel.Verbose, "unable to parse minimum version {0}. Bypassing plugin load.", minimumVersion); - return false; - } - - - // Check for beta build version - if (runtimeVer.Major != 0) - { - return runtimeVer.CompareTo(minimumVer) >= 0; - } - - Debug.LogMessage(LogEventLevel.Verbose, "Running Local Build. Bypassing Dependency Check."); - return true; - - /* - var minVersion = Regex.Match(minimumVersion, @"^(\d*).(\d*).(\d*)$"); - - if(!minVersion.Success) - { + if (String.IsNullOrEmpty(minimumVersion)) + { + Debug.LogMessage(LogEventLevel.Information,"Plugin does not specify a minimum version. Loading plugin may not work as expected. Proceeding with loading plugin"); + return true; + } + var runtimeVersion = Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*).*"); + + var runtimeVersionMajor = Int16.Parse(runtimeVersion.Groups[1].Value); + var runtimeVersionMinor = Int16.Parse(runtimeVersion.Groups[2].Value); + var runtimeVersionBuild = Int16.Parse(runtimeVersion.Groups[3].Value); + + var runtimeVer = new Version(runtimeVersionMajor, runtimeVersionMinor, runtimeVersionBuild); + + Version minimumVer; + try + { + minimumVer = new Version(minimumVersion); + } + catch + { + Debug.LogMessage(LogEventLevel.Verbose, "unable to parse minimum version {0}. Bypassing plugin load.", minimumVersion); + return false; + } + + + // Check for beta build version + if (runtimeVer.Major != 0) + { + return runtimeVer.CompareTo(minimumVer) >= 0; + } + + Debug.LogMessage(LogEventLevel.Verbose, "Running Local Build. Bypassing Dependency Check."); + return true; + + /* + var minVersion = Regex.Match(minimumVersion, @"^(\d*).(\d*).(\d*)$"); + + if(!minVersion.Success) + { + + } + + var minVersionMajor = Int16.Parse(minVersion.Groups[1].Value); + var minVersionMinor = Int16.Parse(minVersion.Groups[2].Value); + var minVersionBuild = Int16.Parse(minVersion.Groups[3].Value); + + + + if (minVersionMajor > runtimeVersionMajor) + return false; + + if (minVersionMinor > runtimeVersionMinor) + return false; + + if (minVersionBuild > runtimeVersionBuild) + return false; + + return true; + */ } - var minVersionMajor = Int16.Parse(minVersion.Groups[1].Value); - var minVersionMinor = Int16.Parse(minVersion.Groups[2].Value); - var minVersionBuild = Int16.Parse(minVersion.Groups[3].Value); - - - - if (minVersionMajor > runtimeVersionMajor) - return false; - - if (minVersionMinor > runtimeVersionMinor) - return false; - - if (minVersionBuild > runtimeVersionBuild) - return false; - - return true; - */ - } - static Global() { // Fire up CrestronDataStoreStatic @@ -305,18 +310,6 @@ namespace PepperDash.Essentials.Core CrestronConsole.PrintLine("Error starting CrestronDataStoreStatic: {0}", err); return; } - - try - { - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("en"); - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture("en"); - } - catch (CultureNotFoundException) - { - // If specific culture fails, fall back to invariant - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; - } } } diff --git a/src/PepperDash.Essentials.Core/Global/JobTimer.cs b/src/PepperDash.Essentials.Core/Global/JobTimer.cs index 0d75a972..55814368 100644 --- a/src/PepperDash.Essentials.Core/Global/JobTimer.cs +++ b/src/PepperDash.Essentials.Core/Global/JobTimer.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Timers; +using System.Text; +using Crestron.SimplSharp; namespace PepperDash.Essentials.Core { @@ -10,7 +11,7 @@ namespace PepperDash.Essentials.Core /// public static class JobTimer { - static Timer MinuteTimer; + static CTimer MinuteTimer; static List Items = new List(); @@ -41,9 +42,7 @@ namespace PepperDash.Essentials.Core { if (Items.Count > 0 && MinuteTimer == null) { - MinuteTimer = new Timer(60000); - MinuteTimer.Elapsed += (sender, e) => MinuteTimerCallback(); - MinuteTimer.Start(); + MinuteTimer = new CTimer(o => MinuteTimerCallback(), null, 60000, 60000); } } diff --git a/src/PepperDash.Essentials.Core/Global/Scheduler.cs b/src/PepperDash.Essentials.Core/Global/Scheduler.cs index ff1773fe..d3641ffc 100644 --- a/src/PepperDash.Essentials.Core/Global/Scheduler.cs +++ b/src/PepperDash.Essentials.Core/Global/Scheduler.cs @@ -1,100 +1,104 @@ using System; using System.Collections.Generic; +using System.Linq; using Crestron.SimplSharp; +using Crestron.SimplSharp.Reflection; using Crestron.SimplSharp.Scheduler; using PepperDash.Core; +using PepperDash.Essentials.Core.Fusion; using PepperDash.Essentials.Room.Config; using Serilog.Events; +using Activator = System.Activator; -namespace PepperDash.Essentials.Core; - -/// -/// Global Scheduler for the system -/// -public static class Scheduler +namespace PepperDash.Essentials.Core { - private static readonly Dictionary EventGroups = new Dictionary(); - - static Scheduler() + /// + /// Global Scheduler for the system + /// + public static class Scheduler { - CrestronConsole.AddNewConsoleCommand(DeleteEventGroup, "DeleteEventGroup", "Deletes the event group by key", ConsoleAccessLevelEnum.AccessOperator); + private static readonly Dictionary EventGroups = new Dictionary(); - CrestronConsole.AddNewConsoleCommand(ClearEventsFromGroup, "ClearAllEvents", "Clears all scheduled events for this group", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(ListAllEventGroups, "ListAllEventGroups", "Lists all the event groups by key", ConsoleAccessLevelEnum.AccessOperator); - - CrestronConsole.AddNewConsoleCommand(ListAllEventsForGroup, "ListEventsForGroup", - "Lists all events for the given group", ConsoleAccessLevelEnum.AccessOperator); - } - - - static void DeleteEventGroup(string groupName) - { - if (EventGroups.ContainsKey(groupName)) + static Scheduler() { + CrestronConsole.AddNewConsoleCommand(DeleteEventGroup, "DeleteEventGroup", "Deletes the event group by key", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(ClearEventsFromGroup, "ClearAllEvents", "Clears all scheduled events for this group", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(ListAllEventGroups, "ListAllEventGroups", "Lists all the event groups by key", ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(ListAllEventsForGroup, "ListEventsForGroup", + "Lists all events for the given group", ConsoleAccessLevelEnum.AccessOperator); + } + + + static void DeleteEventGroup(string groupName) + { + if (EventGroups.ContainsKey(groupName)) + { + var group = EventGroups[groupName]; + + EventGroups.Remove(groupName); + + group.Dispose(); + + group = null; + } + } + + /// + /// Clears (deletes) all events from a group + /// + /// + static void ClearEventsFromGroup(string groupName) + { + if (!EventGroups.ContainsKey(groupName)) + { + Debug.LogMessage(LogEventLevel.Information, + "[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null, + groupName); + return; + } + var group = EventGroups[groupName]; - EventGroups.Remove(groupName); + if (group != null) + { + group.ClearAllEvents(); - group.Dispose(); - - group = null; - } - } - - /// - /// Clears (deletes) all events from a group - /// - /// - static void ClearEventsFromGroup(string groupName) - { - if (!EventGroups.ContainsKey(groupName)) - { - Debug.LogMessage(LogEventLevel.Information, - "[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null, - groupName); - return; + Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", null, groupName); + } + else + Debug.LogMessage(LogEventLevel.Information, + "[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null, + groupName); } - var group = EventGroups[groupName]; - - if (group != null) + static void ListAllEventGroups(string command) { - group.ClearAllEvents(); - - Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", null, groupName); - } - else - Debug.LogMessage(LogEventLevel.Information, - "[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null, - groupName); - } - - static void ListAllEventGroups(string command) - { - CrestronConsole.ConsoleCommandResponse("Event Groups:"); - foreach (var group in EventGroups) - { - CrestronConsole.ConsoleCommandResponse($"{group.Key}"); - } - } - - static void ListAllEventsForGroup(string args) - { - Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", null, args); - - ScheduledEventGroup group; - - if (!EventGroups.TryGetValue(args, out group)) - { - Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", null, args); - return; + CrestronConsole.ConsoleCommandResponse("Event Groups:"); + foreach (var group in EventGroups) + { + CrestronConsole.ConsoleCommandResponse($"{group.Key}"); + } } - foreach (var evt in group.ScheduledEvents) + static void ListAllEventsForGroup(string args) { - CrestronConsole.ConsoleCommandResponse( + Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", null, args); + + ScheduledEventGroup group; + + if (!EventGroups.TryGetValue(args, out group)) + { + Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", null, args); + return; + } + + foreach (var evt in group.ScheduledEvents) + { + CrestronConsole.ConsoleCommandResponse( $@" ****Event key {evt.Key}**** Event state: {evt.Value.EventState} @@ -104,223 +108,224 @@ Acknowlegable: {evt.Value.Acknowledgeable} Recurrence: {evt.Value.Recurrence.Recurrence} Recurrence Days: {evt.Value.Recurrence.RecurrenceDays} ********************"); + } } - } - /// - /// Adds the event group to the global list - /// - /// - /// - /// AddEventGroup method - /// - public static void AddEventGroup(ScheduledEventGroup eventGroup) - { - // Add this group to the global collection - if (!EventGroups.ContainsKey(eventGroup.Name)) - EventGroups.Add(eventGroup.Name, eventGroup); - } - - /// - /// Removes the event group from the global list - /// - /// - /// - /// RemoveEventGroup method - /// - public static void RemoveEventGroup(ScheduledEventGroup eventGroup) - { - if (!EventGroups.ContainsKey(eventGroup.Name)) - EventGroups.Remove(eventGroup.Name); - } - - /// - /// Gets the event group by key - /// - /// key of the event group - /// - public static ScheduledEventGroup GetEventGroup(string key) - { - ScheduledEventGroup returnValue; - - return EventGroups.TryGetValue(key, out returnValue) ? returnValue : null; - } -} - -/// -/// SchedulerUtilities class -/// -public static class SchedulerUtilities -{ - /// - /// Checks the day of week in eventTime to see if it matches the weekdays defined in the recurrence enum. - /// - /// - /// - /// - /// - /// CheckIfDayOfWeekMatchesRecurrenceDays method - /// - public static bool CheckIfDayOfWeekMatchesRecurrenceDays(DateTime eventTime, ScheduledEventCommon.eWeekDays recurrence) - { - bool isMatch = false; - - var dayOfWeek = eventTime.DayOfWeek; - - Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}", null, dayOfWeek); - switch (dayOfWeek) + /// + /// Adds the event group to the global list + /// + /// + /// + /// AddEventGroup method + /// + public static void AddEventGroup(ScheduledEventGroup eventGroup) { - case DayOfWeek.Sunday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Sunday) == ScheduledEventCommon.eWeekDays.Sunday) - isMatch = true; - break; - } - case DayOfWeek.Monday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Monday) == ScheduledEventCommon.eWeekDays.Monday) - isMatch = true; - break; - } - case DayOfWeek.Tuesday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Tuesday) == ScheduledEventCommon.eWeekDays.Tuesday) - isMatch = true; - break; - } - case DayOfWeek.Wednesday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Wednesday) == ScheduledEventCommon.eWeekDays.Wednesday) - isMatch = true; - break; - } - case DayOfWeek.Thursday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Thursday) == ScheduledEventCommon.eWeekDays.Thursday) - isMatch = true; - break; - } - case DayOfWeek.Friday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Friday) == ScheduledEventCommon.eWeekDays.Friday) - isMatch = true; - break; - } - case DayOfWeek.Saturday: - { - if ((recurrence & ScheduledEventCommon.eWeekDays.Saturday) == ScheduledEventCommon.eWeekDays.Saturday) - isMatch = true; - break; - } + // Add this group to the global collection + if (!EventGroups.ContainsKey(eventGroup.Name)) + EventGroups.Add(eventGroup.Name, eventGroup); } - Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week matches recurrence days: {0}", isMatch); - return isMatch; - } - - - /// - /// CheckEventTimeForMatch method - /// - public static bool CheckEventTimeForMatch(ScheduledEvent evnt, DateTime time) - { - return evnt.DateAndTime.Hour == time.Hour && evnt.DateAndTime.Minute == time.Minute; - } - - /// - /// CheckEventRecurrenceForMatch method - /// - public static bool CheckEventRecurrenceForMatch(ScheduledEvent evnt, ScheduledEventCommon.eWeekDays days) - { - return evnt.Recurrence.RecurrenceDays == days; - } - - /// - /// CreateEventFromConfig method - /// - public static void CreateEventFromConfig(ScheduledEventConfig config, ScheduledEventGroup group, ScheduledEvent.UserEventCallBack handler) - { - try + /// + /// Removes the event group from the global list + /// + /// + /// + /// RemoveEventGroup method + /// + public static void RemoveEventGroup(ScheduledEventGroup eventGroup) { - if (group == null) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null", null, null); - return; - } - var scheduledEvent = new ScheduledEvent(config.Key, group) - { - Acknowledgeable = config.Acknowledgeable, - Persistent = config.Persistent - }; - - scheduledEvent.UserCallBack += handler; - - scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); - - var eventTime = DateTime.Parse(config.Time); - - if (DateTime.Now > eventTime) - { - eventTime = eventTime.AddDays(1); - } - - Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", null, eventTime.DayOfWeek, - config.Days); - - var dayOfWeekConverted = ConvertDayOfWeek(eventTime); - - Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", null, dayOfWeekConverted); - - while (!dayOfWeekConverted.IsFlagSet(config.Days)) - { - eventTime = eventTime.AddDays(1); - - dayOfWeekConverted = ConvertDayOfWeek(eventTime); - } - - scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime); - - scheduledEvent.Recurrence.Weekly(config.Days); - - Debug.LogMessage(LogEventLevel.Verbose, $"[Scheduler] Event State: {scheduledEvent.EventState}", null, null); - - if (config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Enabled) - { - scheduledEvent.Enable(); - } - else if (!config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Disabled) - { - scheduledEvent.Disable(); - } - + if(!EventGroups.ContainsKey(eventGroup.Name)) + EventGroups.Remove(eventGroup.Name); } - catch (Exception e) + + /// + /// Gets the event group by key + /// + /// key of the event group + /// + public static ScheduledEventGroup GetEventGroup(string key) { + ScheduledEventGroup returnValue; - Debug.LogMessage(LogEventLevel.Error, "Error creating scheduled event: {0}", null, e); + return EventGroups.TryGetValue(key, out returnValue) ? returnValue : null; } } - private static ScheduledEventCommon.eWeekDays ConvertDayOfWeek(DateTime eventTime) + /// + /// SchedulerUtilities class + /// + public static class SchedulerUtilities { - return (ScheduledEventCommon.eWeekDays)Enum.Parse(typeof(ScheduledEventCommon.eWeekDays), eventTime.DayOfWeek.ToString(), true); + /// + /// Checks the day of week in eventTime to see if it matches the weekdays defined in the recurrence enum. + /// + /// + /// + /// + /// + /// CheckIfDayOfWeekMatchesRecurrenceDays method + /// + public static bool CheckIfDayOfWeekMatchesRecurrenceDays(DateTime eventTime, ScheduledEventCommon.eWeekDays recurrence) + { + bool isMatch = false; + + var dayOfWeek = eventTime.DayOfWeek; + + Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}",null, dayOfWeek); + switch (dayOfWeek) + { + case DayOfWeek.Sunday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Sunday) == ScheduledEventCommon.eWeekDays.Sunday) + isMatch = true; + break; + } + case DayOfWeek.Monday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Monday) == ScheduledEventCommon.eWeekDays.Monday) + isMatch = true; + break; + } + case DayOfWeek.Tuesday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Tuesday) == ScheduledEventCommon.eWeekDays.Tuesday) + isMatch = true; + break; + } + case DayOfWeek.Wednesday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Wednesday) == ScheduledEventCommon.eWeekDays.Wednesday) + isMatch = true; + break; + } + case DayOfWeek.Thursday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Thursday) == ScheduledEventCommon.eWeekDays.Thursday) + isMatch = true; + break; + } + case DayOfWeek.Friday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Friday) == ScheduledEventCommon.eWeekDays.Friday) + isMatch = true; + break; + } + case DayOfWeek.Saturday: + { + if ((recurrence & ScheduledEventCommon.eWeekDays.Saturday) == ScheduledEventCommon.eWeekDays.Saturday) + isMatch = true; + break; + } + } + + Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week matches recurrence days: {0}", isMatch); + + return isMatch; + } + + /// + /// CheckEventTimeForMatch method + /// + public static bool CheckEventTimeForMatch(ScheduledEvent evnt, DateTime time) + { + return evnt.DateAndTime.Hour == time.Hour && evnt.DateAndTime.Minute == time.Minute; + } + + /// + /// CheckEventRecurrenceForMatch method + /// + public static bool CheckEventRecurrenceForMatch(ScheduledEvent evnt, ScheduledEventCommon.eWeekDays days) + { + return evnt.Recurrence.RecurrenceDays == days; + } + + /// + /// CreateEventFromConfig method + /// + public static void CreateEventFromConfig(ScheduledEventConfig config, ScheduledEventGroup group, ScheduledEvent.UserEventCallBack handler) + { + try + { + if (group == null) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null", null, null); + return; + } + var scheduledEvent = new ScheduledEvent(config.Key, group) + { + Acknowledgeable = config.Acknowledgeable, + Persistent = config.Persistent + }; + + scheduledEvent.UserCallBack += handler; + + scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); + + var eventTime = DateTime.Parse(config.Time); + + if (DateTime.Now > eventTime) + { + eventTime = eventTime.AddDays(1); + } + + Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", null, eventTime.DayOfWeek, + config.Days); + + var dayOfWeekConverted = ConvertDayOfWeek(eventTime); + + Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", null, dayOfWeekConverted); + + while (!dayOfWeekConverted.IsFlagSet(config.Days)) + { + eventTime = eventTime.AddDays(1); + + dayOfWeekConverted = ConvertDayOfWeek(eventTime); + } + + scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime); + + scheduledEvent.Recurrence.Weekly(config.Days); + + Debug.LogMessage(LogEventLevel.Verbose, $"[Scheduler] Event State: {scheduledEvent.EventState}", null, null); + + if (config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Enabled) + { + scheduledEvent.Enable(); + } + else if (!config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Disabled) + { + scheduledEvent.Disable(); + } + + } + catch (Exception e) + { + + Debug.LogMessage(LogEventLevel.Error, "Error creating scheduled event: {0}", null, e); + } + } + + private static ScheduledEventCommon.eWeekDays ConvertDayOfWeek(DateTime eventTime) + { + return (ScheduledEventCommon.eWeekDays) Enum.Parse(typeof(ScheduledEventCommon.eWeekDays), eventTime.DayOfWeek.ToString(), true); + } + + private static bool IsFlagSet(this T value, T flag) where T : struct + { + CheckIsEnum(true); + + var lValue = Convert.ToInt64(value); + var lFlag = Convert.ToInt64(flag); + + return (lValue & lFlag) != 0; + } + + private static void CheckIsEnum(bool withFlags) + { + if (!typeof(T).IsEnum) + throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName)); + if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute))) + throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName)); + } } - - private static bool IsFlagSet(this T value, T flag) where T : struct - { - CheckIsEnum(true); - - var lValue = Convert.ToInt64(value); - var lFlag = Convert.ToInt64(flag); - - return (lValue & lFlag) != 0; - } - - private static void CheckIsEnum(bool withFlags) - { - if (!typeof(T).IsEnum) - throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName)); - if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute))) - throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName)); - } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/InUseTracking/IInUseTracking.cs b/src/PepperDash.Essentials.Core/InUseTracking/IInUseTracking.cs index 440b14bd..59ea87b3 100644 --- a/src/PepperDash.Essentials.Core/InUseTracking/IInUseTracking.cs +++ b/src/PepperDash.Essentials.Core/InUseTracking/IInUseTracking.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines a class that uses an InUseTracker /// @@ -15,4 +15,5 @@ namespace PepperDash.Essentials.Core; /// Gets the InUseTracker /// InUseTracking InUseTracker { get; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/InUseTracking/InUseTracking.cs b/src/PepperDash.Essentials.Core/InUseTracking/InUseTracking.cs index 247e8548..c0fcdbf6 100644 --- a/src/PepperDash.Essentials.Core/InUseTracking/InUseTracking.cs +++ b/src/PepperDash.Essentials.Core/InUseTracking/InUseTracking.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Provides in use tracking. Objects can register with this. InUseFeedback can provide /// events when usage changes. @@ -113,4 +113,5 @@ namespace PepperDash.Essentials.Core; // Tracker = tracker; // EventType = eventType; // } - //} \ No newline at end of file + //} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Interfaces/ILogStrings.cs b/src/PepperDash.Essentials.Core/Interfaces/ILogStrings.cs new file mode 100644 index 00000000..b41a9020 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Interfaces/ILogStrings.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core.Interfaces +{ + /// + /// Defines the contract for ILogStrings + /// + [Obsolete("ILogStrings is no longer supported and will be removed in a future release.")] + public interface ILogStrings : IKeyed + { + /// + /// Defines a class that is capable of logging a string + /// + void SendToLog(IKeyed device, string logMessage); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Interfaces/ILogStringsWithLevel.cs b/src/PepperDash.Essentials.Core/Interfaces/ILogStringsWithLevel.cs new file mode 100644 index 00000000..32ef104a --- /dev/null +++ b/src/PepperDash.Essentials.Core/Interfaces/ILogStringsWithLevel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; + +namespace PepperDash.Essentials.Core.Interfaces +{ + /// + /// Defines the contract for ILogStringsWithLevel + /// + [Obsolete("ILogStringsWithLevel is no longer supported and will be removed in a future release.")] + public interface ILogStringsWithLevel : IKeyed + { + /// + /// Defines a class that is capable of logging a string with an int level + /// + void SendToLog(IKeyed device, Debug.ErrorLogLevel level, string logMessage); + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/JoinMaps/JoinMapBase.cs b/src/PepperDash.Essentials.Core/JoinMaps/JoinMapBase.cs index 9cd6776e..d2e7d036 100644 --- a/src/PepperDash.Essentials.Core/JoinMaps/JoinMapBase.cs +++ b/src/PepperDash.Essentials.Core/JoinMaps/JoinMapBase.cs @@ -15,526 +15,648 @@ using PepperDash.Essentials.Core.Config; using Newtonsoft.Json; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -public static class JoinMapHelper +namespace PepperDash.Essentials.Core { /// - /// Attempts to get the serialized join map from config + /// JoinMapHelper class /// - /// - /// - public static string GetSerializedJoinMapForDevice(string joinMapKey) + public static class JoinMapHelper { - if (string.IsNullOrEmpty(joinMapKey)) - return null; - - var joinMap = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; - - return joinMap.ToString(); - } - - /// - /// Attempts to get the serialized join map from config - /// - /// - /// - public static string GetJoinMapForDevice(string joinMapKey) - { - return GetSerializedJoinMapForDevice(joinMapKey); - } - - /// - /// Attempts to find a custom join map by key and returns it deserialized if found - /// - /// - /// - public static Dictionary TryGetJoinMapAdvancedForDevice(string joinMapKey) - { - try + /// + /// Attempts to get the serialized join map from config + /// + /// + /// + /// + /// GetSerializedJoinMapForDevice method + /// + public static string GetSerializedJoinMapForDevice(string joinMapKey) { if (string.IsNullOrEmpty(joinMapKey)) return null; - if (!ConfigReader.ConfigObject.JoinMaps.ContainsKey(joinMapKey)) + var joinMap = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; + + return joinMap.ToString(); + } + + /// + /// Attempts to get the serialized join map from config + /// + /// + /// + /// + /// GetJoinMapForDevice method + /// + public static string GetJoinMapForDevice(string joinMapKey) + { + return GetSerializedJoinMapForDevice(joinMapKey); + } + + /// + /// Attempts to find a custom join map by key and returns it deserialized if found + /// + /// + /// + public static Dictionary TryGetJoinMapAdvancedForDevice(string joinMapKey) + { + try { - Debug.LogMessage(LogEventLevel.Verbose, "No Join Map found in config with key: '{0}'", joinMapKey); + if (string.IsNullOrEmpty(joinMapKey)) + return null; + + if (!ConfigReader.ConfigObject.JoinMaps.ContainsKey(joinMapKey)) + { + Debug.LogMessage(LogEventLevel.Verbose, "No Join Map found in config with key: '{0}'", joinMapKey); + return null; + } + + Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load custom join map with key: {0}", joinMapKey); + + var joinMapJToken = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; + + if (joinMapJToken == null) + return null; + + var joinMapData = joinMapJToken.ToObject>(); + + return joinMapData; + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, "Error getting join map for key: '{0}'. Error: {1}", joinMapKey, e); return null; } - - Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load custom join map with key: {0}", joinMapKey); - - var joinMapJToken = ConfigReader.ConfigObject.JoinMaps[joinMapKey]; - - if (joinMapJToken == null) - return null; - - var joinMapData = joinMapJToken.ToObject>(); - - return joinMapData; - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, "Error getting join map for key: '{0}'. Error: {1}", joinMapKey, e); - return null; } + } -} - -/// -/// Base class for join maps -/// -public abstract class JoinMapBaseAdvanced -{ - protected uint JoinOffset; - /// - /// The collection of joins and associated metadata + /// Base class for join maps /// - public Dictionary Joins { get; private set; } - - protected JoinMapBaseAdvanced(uint joinStart) + public abstract class JoinMapBaseAdvanced { - Joins = new Dictionary(); + /// + /// The join offset + /// + protected uint JoinOffset; - JoinOffset = joinStart - 1; - } + /// + /// The collection of joins and associated metadata + /// + public Dictionary Joins { get; private set; } - protected JoinMapBaseAdvanced(uint joinStart, Type type):this(joinStart) - { - AddJoins(type); - } - - protected void AddJoins(Type type) - { - var fields = - type.GetFields(BindingFlags.Public | BindingFlags.Instance) - .Where(f => f.IsDefined(typeof (JoinNameAttribute), true)).ToList(); - - Debug.LogMessage(LogEventLevel.Debug, "Got {fields} with JoinNameAttribute", fields.Count); - - foreach (var field in fields) + /// + /// Constructor + /// + /// join start offset + protected JoinMapBaseAdvanced(uint joinStart) { - var childClass = Convert.ChangeType(this, type, null); + Joins = new Dictionary(); - //this here is JoinMapBaseAdvanced, not the child class. JoinMapBaseAdvanced has no fields. + JoinOffset = joinStart - 1; + } - if (!(field.GetValue(childClass) is JoinDataComplete value)) + /// + /// Constructor + /// + /// join start offset + /// type of joinstart + protected JoinMapBaseAdvanced(uint joinStart, Type type):this(joinStart) + { + AddJoins(type); + } + + /// + /// AddJoins method + /// + /// type of join to add + protected void AddJoins(Type type) + { + var fields = + type.GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(f => f.IsDefined(typeof (JoinNameAttribute), true)).ToList(); + + Debug.LogMessage(LogEventLevel.Debug, "Got {fields} with JoinNameAttribute", fields.Count); + + foreach (var field in fields) { - Debug.LogMessage(LogEventLevel.Information, "Unable to cast base class to {0}", type.Name); - continue; + var childClass = Convert.ChangeType(this, type, null); + + //this here is JoinMapBaseAdvanced, not the child class. JoinMapBaseAdvanced has no fields. + + if (!(field.GetValue(childClass) is JoinDataComplete value)) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to cast base class to {0}", type.Name); + continue; + } + + value.SetJoinOffset(JoinOffset); + + var joinName = value.GetNameAttribute(field); + + if (string.IsNullOrEmpty(joinName)) continue; + + Joins.Add(joinName, value); } - value.SetJoinOffset(JoinOffset); - var joinName = value.GetNameAttribute(field); + if (Debug.Level > 0) + { + PrintJoinMapInfo(); + } + } - if (string.IsNullOrEmpty(joinName)) continue; + /// + /// PrintJoinMapInfo method + /// + public void PrintJoinMapInfo() + { + var sb = JoinmapStringBuilder(); - Joins.Add(joinName, value); + CrestronConsole.ConsoleCommandResponse(sb.ToString()); + } + + private StringBuilder JoinmapStringBuilder() + { + var sb = new StringBuilder(); + + var lineEnding = "\r\n"; + + var digitals = + Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Digital)) + .ToDictionary(j => j.Key, j => j.Value); + + var analogs = Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Analog)) + .ToDictionary(j => j.Key, j => j.Value); + + var serials = + Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Serial)) + .ToDictionary(j => j.Key, j => j.Value); + + Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Digital join count {digitalCount} Analog join count {analogCount} Serial join count {serialCount}", null, digitals.Count, analogs.Count, serials.Count); + + // Get the joins of each type and print them + sb.Append($"# {GetType().Name}\r\n"); + sb.Append(lineEnding); + sb.Append($"## Digitals{lineEnding}"); + sb.Append(lineEnding); + // Get the joins of each type and print them + + var digitalSb = AppendJoinList(GetSortedJoins(digitals)); + digitalSb.Append($"## Analogs{lineEnding}"); + digitalSb.Append(lineEnding); + + var analogSb = AppendJoinList(GetSortedJoins(analogs)); + analogSb.Append($"## Serials{lineEnding}"); + analogSb.Append(lineEnding); + + + var serialSb = AppendJoinList(GetSortedJoins(serials)); + + sb.EnsureCapacity(sb.Length + digitalSb.Length + analogSb.Length + serialSb.Length); + sb.Append(digitalSb).Append(analogSb).Append(serialSb); + return sb; + } + + /// + /// MarkdownJoinMapInfo method + /// + public void MarkdownJoinMapInfo(string deviceKey, string bridgeKey) + { + var pluginType = GetType().Name; + + CrestronConsole.ConsoleCommandResponse("{0}:\n", pluginType); + + + + WriteJoinmapMarkdown(JoinmapStringBuilder(), pluginType, bridgeKey, deviceKey); + + } + + private static void WriteJoinmapMarkdown(StringBuilder stringBuilder, string pluginType, string bridgeKey, string deviceKey) + { + var fileName = string.Format("{0}{1}{2}__{3}__{4}.md", Global.FilePathPrefix, "joinMaps/", pluginType, bridgeKey, deviceKey); + + using (var sw = new StreamWriter(fileName)) + { + sw.WriteLine(stringBuilder.ToString()); + CrestronConsole.ConsoleCommandResponse("Joinmap Readme generated and written to {0}", fileName); + } + + } + + /// + /// Returns a sorted list by JoinNumber + /// + /// + /// + static List> GetSortedJoins(Dictionary joins) + { + var sortedJoins = joins.ToList(); + + sortedJoins.Sort((pair1, pair2) => pair1.Value.JoinNumber.CompareTo(pair2.Value.JoinNumber)); + + return sortedJoins; } - if (Debug.Level > 0) + static StringBuilder AppendJoinList(List> joins) { + var sb = new StringBuilder(); + const string stringFormatter = "| {0} | {1} | {2} | {3} | {4} |\r\n"; + const int joinNumberLen = 11; + const int joinSpanLen = 9; + const int typeLen = 19; + const int capabilitiesLen = 12; + var descriptionLen = (from @join in joins select @join.Value into j select j.Metadata.Description.Length).Concat(new[] {11}).Max(); + + //build header + sb.Append(string.Format(stringFormatter, + string.Format("Join Number").PadRight(joinNumberLen, ' '), + string.Format("Join Span").PadRight(joinSpanLen, ' '), + string.Format("Description").PadRight(descriptionLen, ' '), + string.Format("Type").PadRight(typeLen, ' '), + string.Format("Capabilities").PadRight(capabilitiesLen, ' '))); + //build table seperator + sb.Append(string.Format(stringFormatter, + new string('-', joinNumberLen), + new string('-', joinSpanLen), + new string('-', descriptionLen), + new string('-', typeLen), + new string('-', capabilitiesLen))); + + foreach (var join in joins) + { + sb.Append(join.Value.GetMarkdownFormattedData(stringFormatter, descriptionLen)); + } + sb.Append("\r\n"); + return sb; + } + + /// + /// Attempts to find the matching key for the custom join and if found overwrites the default JoinData with the custom + /// + /// + /// + /// SetCustomJoinData method + /// + public void SetCustomJoinData(Dictionary joinData) + { + foreach (var customJoinData in joinData) + { + JoinDataComplete join; + + if (!Joins.TryGetValue(customJoinData.Key, out join)) + { + Debug.LogMessage(LogEventLevel.Verbose, "No matching key found in join map for: '{0}'", customJoinData.Key); + continue; + } + + if (join != null) + { + join.SetCustomJoinData(customJoinData.Value); + } + } + PrintJoinMapInfo(); } + + // /// + // /// Returns the join span for the join with the specified key + // /// + // /// + // /// + //public uint GetJoinSpanForKey(string key) + //{ + // return Joins.ContainsKey(key) ? Joins[key].JoinSpan : 0; + //} } /// - /// Prints the join information to console + /// Read = Provides feedback to SIMPL + /// Write = Responds to sig values from SIMPL /// - public void PrintJoinMapInfo() + [Flags] + public enum eJoinCapabilities { - var sb = JoinmapStringBuilder(); + /// + /// No capabilities + /// + None = 0, - CrestronConsole.ConsoleCommandResponse(sb.ToString()); - } + /// + /// To SIMPL + /// + ToSIMPL = 1, - private StringBuilder JoinmapStringBuilder() - { - var sb = new StringBuilder(); + /// + /// From SIMPL + /// + FromSIMPL = 2, - var lineEnding = "\r\n"; + /// + /// To and From SIMPL + /// + ToFromSIMPL = ToSIMPL | FromSIMPL, - var digitals = - Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Digital)) - .ToDictionary(j => j.Key, j => j.Value); + /// + /// To Fusion + /// + ToFusion = 4, - var analogs = Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Analog)) - .ToDictionary(j => j.Key, j => j.Value); + /// + /// From Fusion + /// + FromFusion = 8, - var serials = - Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Serial)) - .ToDictionary(j => j.Key, j => j.Value); - - Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Digital join count {digitalCount} Analog join count {analogCount} Serial join count {serialCount}", null, digitals.Count, analogs.Count, serials.Count); - - // Get the joins of each type and print them - sb.Append($"# {GetType().Name}\r\n"); - sb.Append(lineEnding); - sb.Append($"## Digitals{lineEnding}"); - sb.Append(lineEnding); - // Get the joins of each type and print them - - var digitalSb = AppendJoinList(GetSortedJoins(digitals)); - digitalSb.Append($"## Analogs{lineEnding}"); - digitalSb.Append(lineEnding); - - var analogSb = AppendJoinList(GetSortedJoins(analogs)); - analogSb.Append($"## Serials{lineEnding}"); - analogSb.Append(lineEnding); - - - var serialSb = AppendJoinList(GetSortedJoins(serials)); - - sb.EnsureCapacity(sb.Length + digitalSb.Length + analogSb.Length + serialSb.Length); - sb.Append(digitalSb).Append(analogSb).Append(serialSb); - return sb; + /// + /// To and From Fusion + /// + ToFromFusion = ToFusion | FromFusion, } /// - /// Prints the join information to console + /// Enumeration of eJoinType values /// - public void MarkdownJoinMapInfo(string deviceKey, string bridgeKey) + [Flags] + public enum eJoinType { - var pluginType = GetType().Name; + /// + /// No join type + /// + None = 0, - CrestronConsole.ConsoleCommandResponse("{0}:\n", pluginType); + /// + /// Digital join + /// + Digital = 1, + /// + /// Analog join + /// + Analog = 2, + /// + /// Serial join + /// + Serial = 4, - WriteJoinmapMarkdown(JoinmapStringBuilder(), pluginType, bridgeKey, deviceKey); + /// + /// Digital and Analog join + /// + DigitalAnalog = Digital | Analog, + + /// + /// Digital and Serial join + /// + DigitalSerial = Digital | Serial, + + /// + /// Analog and Serial join + /// + AnalogSerial = Analog | Serial, + + /// + /// Digital, Analog and Serial join + /// + DigitalAnalogSerial = Digital | Analog | Serial, + } + + /// + /// Represents a JoinMetadata + /// + public class JoinMetadata + { + private string _description; + /// + /// A description for the join to better describe its usage + /// + [JsonProperty("description")] + public string Description { get { return _description; } set { _description = value; } } + + /// + /// Gets or sets the JoinType + /// + [JsonProperty("joinType")] + public eJoinType JoinType { get; set; } + + /// + /// Gets or sets the JoinCapabilities + /// + [JsonProperty("joinCapabilities")] + public eJoinCapabilities JoinCapabilities { get; set; } + + /// + /// Gets or sets the ValidValues + /// + [JsonProperty("validValues")] + public string[] ValidValues { get; set; } } - private static void WriteJoinmapMarkdown(StringBuilder stringBuilder, string pluginType, string bridgeKey, string deviceKey) + /// + /// Data describing the join. Can be overridden from configuratino + /// + public class JoinData { - var fileName = string.Format("{0}{1}{2}__{3}__{4}.md", Global.FilePathPrefix, "joinMaps/", pluginType, bridgeKey, deviceKey); + /// + /// Join number (based on join offset value) + /// + [JsonProperty("joinNumber")] + public uint JoinNumber { get; set; } - using (var sw = new StreamWriter(fileName)) + /// + /// Join range span. If join indicates the start of a range of joins, this indicated the maximum number of joins in the range + /// + [JsonProperty("joinSpan")] + public uint JoinSpan { get; set; } + + /// + /// Gets or sets the AttributeName + /// + [JsonProperty("attributeName")] + public string AttributeName { get; set; } + } + + /// + /// A class to aggregate the JoinData and JoinMetadata for a join + /// + public class JoinDataComplete + { + private uint _joinOffset; + + private JoinData _data; + + /// + /// Gets or sets the JoinMetadata + /// + public JoinMetadata Metadata { get; set; } + + /// + /// To store some future information as you please + /// + public object UserObject { get; private set; } + + /// + /// Constructor + /// + /// + /// + public JoinDataComplete(JoinData data, JoinMetadata metadata) { - sw.WriteLine(stringBuilder.ToString()); - CrestronConsole.ConsoleCommandResponse("Joinmap Readme generated and written to {0}", fileName); + _data = data; + Metadata = metadata; } - } - - /// - /// Returns a sorted list by JoinNumber - /// - /// - /// - static List> GetSortedJoins(Dictionary joins) - { - var sortedJoins = joins.ToList(); - - sortedJoins.Sort((pair1, pair2) => pair1.Value.JoinNumber.CompareTo(pair2.Value.JoinNumber)); - - return sortedJoins; - } - - - static StringBuilder AppendJoinList(List> joins) - { - var sb = new StringBuilder(); - const string stringFormatter = "| {0} | {1} | {2} | {3} | {4} |\r\n"; - const int joinNumberLen = 11; - const int joinSpanLen = 9; - const int typeLen = 19; - const int capabilitiesLen = 12; - var descriptionLen = (from @join in joins select @join.Value into j select j.Metadata.Description.Length).Concat(new[] {11}).Max(); - - //build header - sb.Append(string.Format(stringFormatter, - string.Format("Join Number").PadRight(joinNumberLen, ' '), - string.Format("Join Span").PadRight(joinSpanLen, ' '), - string.Format("Description").PadRight(descriptionLen, ' '), - string.Format("Type").PadRight(typeLen, ' '), - string.Format("Capabilities").PadRight(capabilitiesLen, ' '))); - //build table seperator - sb.Append(string.Format(stringFormatter, - new string('-', joinNumberLen), - new string('-', joinSpanLen), - new string('-', descriptionLen), - new string('-', typeLen), - new string('-', capabilitiesLen))); - - foreach (var join in joins) + /// + /// GetMarkdownFormattedData method + /// + /// formatter to use + /// length of the description + /// + public string GetMarkdownFormattedData(string stringFormatter, int descriptionLen) { - sb.Append(join.Value.GetMarkdownFormattedData(stringFormatter, descriptionLen)); - } - sb.Append("\r\n"); - return sb; - } - /// - /// Attempts to find the matching key for the custom join and if found overwrites the default JoinData with the custom - /// - /// - public void SetCustomJoinData(Dictionary joinData) - { - foreach (var customJoinData in joinData) - { - JoinDataComplete join; + //Fixed Width Headers + var joinNumberLen = string.Format("Join Number").Length; + var joinSpanLen = string.Format("Join Span").Length; + var typeLen = string.Format("AnalogDigitalSerial").Length; + var capabilitiesLen = string.Format("ToFromFusion").Length; - if (!Joins.TryGetValue(customJoinData.Key, out join)) + //Track which one failed, if it did + const string placeholder = "unknown"; + var dataArray = new Dictionary { - Debug.LogMessage(LogEventLevel.Verbose, "No matching key found in join map for: '{0}'", customJoinData.Key); - continue; + {"joinNumber", placeholder.PadRight(joinNumberLen, ' ')}, + {"joinSpan", placeholder.PadRight(joinSpanLen, ' ')}, + {"description", placeholder.PadRight(descriptionLen, ' ')}, + {"joinType", placeholder.PadRight(typeLen, ' ')}, + {"capabilities", placeholder.PadRight(capabilitiesLen, ' ')} + }; + + + try + { + dataArray["joinNumber"] = string.Format("{0}", JoinNumber.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinNumberLen, ' '); + dataArray["joinSpan"] = string.Format("{0}", JoinSpan.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinSpanLen, ' '); + dataArray["description"] = string.Format("{0}", Metadata.Description.ReplaceIfNullOrEmpty(placeholder)).PadRight(descriptionLen, ' '); + dataArray["joinType"] = string.Format("{0}", Metadata.JoinType.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(typeLen, ' '); + dataArray["capabilities"] = string.Format("{0}", Metadata.JoinCapabilities.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(capabilitiesLen, ' '); + + return string.Format(stringFormatter, + dataArray["joinNumber"], + dataArray["joinSpan"], + dataArray["description"], + dataArray["joinType"], + dataArray["capabilities"]); + } - - if (join != null) + catch (Exception e) { - join.SetCustomJoinData(customJoinData.Value); + //Don't Throw - we don't want to kill the system if this falls over - it's not mission critical. Print the error, use placeholder data + var errorKey = string.Empty; + foreach (var item in dataArray) + { + if (item.Value.TrimEnd() == placeholder) continue; + errorKey = item.Key; + break; + } + Debug.LogMessage(LogEventLevel.Information, "Unable to decode join metadata {1}- {0}", e.Message, !string.IsNullOrEmpty(errorKey) ? (' ' + errorKey) : string.Empty); + return string.Format(stringFormatter, + dataArray["joinNumber"], + dataArray["joinSpan"], + dataArray["description"], + dataArray["joinType"], + dataArray["capabilities"]); } } - PrintJoinMapInfo(); - } - ///// - ///// Returns the join number for the join with the specified key - ///// - ///// - ///// - //public uint GetJoinForKey(string key) - //{ - // return Joins.ContainsKey(key) ? Joins[key].JoinNumber : 0; - //} - - - ///// - ///// Returns the join span for the join with the specified key - ///// - ///// - ///// - //public uint GetJoinSpanForKey(string key) - //{ - // return Joins.ContainsKey(key) ? Joins[key].JoinSpan : 0; - //} -} - -/// -/// Read = Provides feedback to SIMPL -/// Write = Responds to sig values from SIMPL -/// -[Flags] -public enum eJoinCapabilities -{ - None = 0, - ToSIMPL = 1, - FromSIMPL = 2, - ToFromSIMPL = ToSIMPL | FromSIMPL, - ToFusion = 4, - FromFusion = 8, - ToFromFusion = ToFusion | FromFusion, -} - -[Flags] -public enum eJoinType -{ - None = 0, - Digital = 1, - Analog = 2, - Serial = 4, - DigitalAnalog = Digital | Analog, - DigitalSerial = Digital | Serial, - AnalogSerial = Analog | Serial, - DigitalAnalogSerial = Digital | Analog | Serial, -} - -/// -/// Metadata describing the join -/// -public class JoinMetadata -{ - private string _description; - /// - /// A description for the join to better describe its usage - /// - [JsonProperty("description")] - public string Description { get { return _description; } set { _description = value; } } - /// - /// Signal type(s) - /// - [JsonProperty("joinType")] - public eJoinType JoinType { get; set; } - /// - /// Indicates whether the join is read and/or write - /// - [JsonProperty("joinCapabilities")] - public eJoinCapabilities JoinCapabilities { get; set; } - /// - /// Indicates a set of valid values (particularly if this translates to an enum - /// - [JsonProperty("validValues")] - public string[] ValidValues { get; set; } - -} - -/// -/// Data describing the join. Can be overridden from configuratino -/// -public class JoinData -{ - /// - /// Join number (based on join offset value) - /// - [JsonProperty("joinNumber")] - public uint JoinNumber { get; set; } - /// - /// Join range span. If join indicates the start of a range of joins, this indicated the maximum number of joins in the range - /// - [JsonProperty("joinSpan")] - public uint JoinSpan { get; set; } - /// - /// Fusion Attribute Name (optional) - /// - [JsonProperty("attributeName")] - public string AttributeName { get; set; } -} - -/// -/// A class to aggregate the JoinData and JoinMetadata for a join -/// -public class JoinDataComplete -{ - private uint _joinOffset; - - private JoinData _data; - public JoinMetadata Metadata { get; set; } - /// - /// To store some future information as you please - /// - public object UserObject { get; private set; } - - public JoinDataComplete(JoinData data, JoinMetadata metadata) - { - _data = data; - Metadata = metadata; - } - - public string GetMarkdownFormattedData(string stringFormatter, int descriptionLen) - { - - //Fixed Width Headers - var joinNumberLen = string.Format("Join Number").Length; - var joinSpanLen = string.Format("Join Span").Length; - var typeLen = string.Format("AnalogDigitalSerial").Length; - var capabilitiesLen = string.Format("ToFromFusion").Length; - - //Track which one failed, if it did - const string placeholder = "unknown"; - var dataArray = new Dictionary + /// + /// Sets the join offset value + /// + /// + public void SetJoinOffset(uint joinOffset) { - {"joinNumber", placeholder.PadRight(joinNumberLen, ' ')}, - {"joinSpan", placeholder.PadRight(joinSpanLen, ' ')}, - {"description", placeholder.PadRight(descriptionLen, ' ')}, - {"joinType", placeholder.PadRight(typeLen, ' ')}, - {"capabilities", placeholder.PadRight(capabilitiesLen, ' ')} - }; - - - try - { - dataArray["joinNumber"] = string.Format("{0}", JoinNumber.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinNumberLen, ' '); - dataArray["joinSpan"] = string.Format("{0}", JoinSpan.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinSpanLen, ' '); - dataArray["description"] = string.Format("{0}", Metadata.Description.ReplaceIfNullOrEmpty(placeholder)).PadRight(descriptionLen, ' '); - dataArray["joinType"] = string.Format("{0}", Metadata.JoinType.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(typeLen, ' '); - dataArray["capabilities"] = string.Format("{0}", Metadata.JoinCapabilities.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(capabilitiesLen, ' '); - - return string.Format(stringFormatter, - dataArray["joinNumber"], - dataArray["joinSpan"], - dataArray["description"], - dataArray["joinType"], - dataArray["capabilities"]); - + _joinOffset = joinOffset; } - catch (Exception e) + + /// + /// The join number (including the offset) + /// + public uint JoinNumber { - //Don't Throw - we don't want to kill the system if this falls over - it's not mission critical. Print the error, use placeholder data - var errorKey = string.Empty; - foreach (var item in dataArray) - { - if (item.Value.TrimEnd() == placeholder) continue; - errorKey = item.Key; - break; - } - Debug.LogMessage(LogEventLevel.Information, "Unable to decode join metadata {1}- {0}", e.Message, !string.IsNullOrEmpty(errorKey) ? (' ' + errorKey) : string.Empty); - return string.Format(stringFormatter, - dataArray["joinNumber"], - dataArray["joinSpan"], - dataArray["description"], - dataArray["joinType"], - dataArray["capabilities"]); + get { return _data.JoinNumber+ _joinOffset; } + set { _data.JoinNumber = value; } + } + + /// + /// The join span + /// + public uint JoinSpan + { + get { return _data.JoinSpan; } + } + + /// + /// The attribute name + /// + public string AttributeName + { + get { return _data.AttributeName; } + } + + /// + /// SetCustomJoinData method + /// + public void SetCustomJoinData(JoinData customJoinData) + { + _data = customJoinData; + } + + /// + /// GetNameAttribute method + /// + public string GetNameAttribute(MemberInfo memberInfo) + { + var name = string.Empty; + var attribute = (JoinNameAttribute)Attribute.GetCustomAttribute(memberInfo, typeof(JoinNameAttribute)); + + if (attribute == null) return name; + + name = attribute.Name; + Debug.LogMessage(LogEventLevel.Verbose, "JoinName Attribute value: {0}", name); + return name; } } + /// - /// Sets the join offset value + /// Represents a JoinNameAttribute /// - /// - public void SetJoinOffset(uint joinOffset) + [AttributeUsage(AttributeTargets.All)] + public class JoinNameAttribute : Attribute { - _joinOffset = joinOffset; - } + private string _Name; - /// - /// The join number (including the offset) - /// - public uint JoinNumber - { - get { return _data.JoinNumber+ _joinOffset; } - set { _data.JoinNumber = value; } - } + /// + /// Constructor + /// + /// name of the attribute + public JoinNameAttribute(string name) + { + Debug.LogMessage(LogEventLevel.Verbose, "Setting Attribute Name: {0}",null, name); + _Name = name; + } - public uint JoinSpan - { - get { return _data.JoinSpan; } - } - - public string AttributeName - { - get { return _data.AttributeName; } - } - - public void SetCustomJoinData(JoinData customJoinData) - { - _data = customJoinData; - } - - public string GetNameAttribute(MemberInfo memberInfo) - { - var name = string.Empty; - var attribute = (JoinNameAttribute)Attribute.GetCustomAttribute(memberInfo, typeof(JoinNameAttribute)); - - if (attribute == null) return name; - - name = attribute.Name; - Debug.LogMessage(LogEventLevel.Verbose, "JoinName Attribute value: {0}", name); - return name; - } -} - - - -[AttributeUsage(AttributeTargets.All)] -public class JoinNameAttribute : Attribute -{ - private string _Name; - - public JoinNameAttribute(string name) - { - Debug.LogMessage(LogEventLevel.Verbose, "Setting Attribute Name: {0}",null, name); - _Name = name; - } - - public string Name - { - get { return _Name; } + /// + /// Gets the Name + /// + public string Name + { + get { return _Name; } + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/License/EssentialsLicenseManager.cs b/src/PepperDash.Essentials.Core/License/EssentialsLicenseManager.cs index 9a6d2434..ded7f699 100644 --- a/src/PepperDash.Essentials.Core/License/EssentialsLicenseManager.cs +++ b/src/PepperDash.Essentials.Core/License/EssentialsLicenseManager.cs @@ -11,103 +11,104 @@ using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.License; - -/// -/// Abstract base class for License Managers -/// -public abstract class LicenseManager +namespace PepperDash.Essentials.License { /// - /// Gets or sets the LicenseIsValid + /// Abstract base class for License Managers /// - public BoolFeedback LicenseIsValid { get; protected set; } - - /// - /// Gets or sets the LicenseMessage - /// - public StringFeedback LicenseMessage { get; protected set; } - - /// - /// Gets or sets the LicenseLog - /// - public StringFeedback LicenseLog { get; protected set; } - - /// - /// Constructor - /// - protected LicenseManager() + public abstract class LicenseManager { - CrestronConsole.AddNewConsoleCommand( - s => CrestronConsole.ConsoleCommandResponse(GetStatusString()), - "licensestatus", "shows license and related data", - ConsoleAccessLevelEnum.AccessOperator); + /// + /// Gets or sets the LicenseIsValid + /// + public BoolFeedback LicenseIsValid { get; protected set; } + + /// + /// Gets or sets the LicenseMessage + /// + public StringFeedback LicenseMessage { get; protected set; } + + /// + /// Gets or sets the LicenseLog + /// + public StringFeedback LicenseLog { get; protected set; } + + /// + /// Constructor + /// + protected LicenseManager() + { + CrestronConsole.AddNewConsoleCommand( + s => CrestronConsole.ConsoleCommandResponse(GetStatusString()), + "licensestatus", "shows license and related data", + ConsoleAccessLevelEnum.AccessOperator); + } + + /// + /// Gets the status string for console command + /// + protected abstract string GetStatusString(); } /// - /// Gets the status string for console command + /// Represents a MockEssentialsLicenseManager /// - protected abstract string GetStatusString(); -} - -/// -/// Represents a MockEssentialsLicenseManager -/// -public class MockEssentialsLicenseManager : LicenseManager -{ - /// - /// Returns the singleton mock license manager for this app - /// - public static MockEssentialsLicenseManager Manager + public class MockEssentialsLicenseManager : LicenseManager { - get + /// + /// Returns the singleton mock license manager for this app + /// + public static MockEssentialsLicenseManager Manager { - if (_Manager == null) - _Manager = new MockEssentialsLicenseManager(); - return _Manager; + get + { + if (_Manager == null) + _Manager = new MockEssentialsLicenseManager(); + return _Manager; + } + } + static MockEssentialsLicenseManager _Manager; + + bool IsValid; + + MockEssentialsLicenseManager() : base() + { + LicenseIsValid = new BoolFeedback("LicenseIsValid", + () => { return IsValid; }); + CrestronConsole.AddNewConsoleCommand( + s => SetFromConsole(s.Equals("true", StringComparison.OrdinalIgnoreCase)), + "mocklicense", "true or false for testing", ConsoleAccessLevelEnum.AccessOperator); + + bool valid; + var err = CrestronDataStoreStatic.GetGlobalBoolValue("MockLicense", out valid); + if (err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + SetIsValid(valid); + else if (err == CrestronDataStore.CDS_ERROR.CDS_RECORD_NOT_FOUND) + CrestronDataStoreStatic.SetGlobalBoolValue("MockLicense", false); + else + CrestronConsole.PrintLine("Error restoring Mock License setting: {0}", err); + } + + void SetIsValid(bool isValid) + { + IsValid = isValid; + CrestronDataStoreStatic.SetGlobalBoolValue("MockLicense", isValid); + Debug.LogMessage(LogEventLevel.Information, "Mock License is{0} valid", IsValid ? "" : " not"); + LicenseIsValid.FireUpdate(); + } + + void SetFromConsole(bool isValid) + { + SetIsValid(isValid); + } + + /// + /// Gets the status string for console command + /// + /// license status valid or invalid + protected override string GetStatusString() + { + return string.Format("License Status: {0}", IsValid ? "Valid" : "Not Valid"); } } - static MockEssentialsLicenseManager _Manager; - - bool IsValid; - - MockEssentialsLicenseManager() : base() - { - LicenseIsValid = new BoolFeedback("LicenseIsValid", - () => { return IsValid; }); - CrestronConsole.AddNewConsoleCommand( - s => SetFromConsole(s.Equals("true", StringComparison.OrdinalIgnoreCase)), - "mocklicense", "true or false for testing", ConsoleAccessLevelEnum.AccessOperator); - - bool valid; - var err = CrestronDataStoreStatic.GetGlobalBoolValue("MockLicense", out valid); - if (err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) - SetIsValid(valid); - else if (err == CrestronDataStore.CDS_ERROR.CDS_RECORD_NOT_FOUND) - CrestronDataStoreStatic.SetGlobalBoolValue("MockLicense", false); - else - CrestronConsole.PrintLine("Error restoring Mock License setting: {0}", err); - } - - void SetIsValid(bool isValid) - { - IsValid = isValid; - CrestronDataStoreStatic.SetGlobalBoolValue("MockLicense", isValid); - Debug.LogMessage(LogEventLevel.Information, "Mock License is{0} valid", IsValid ? "" : " not"); - LicenseIsValid.FireUpdate(); - } - - void SetFromConsole(bool isValid) - { - SetIsValid(isValid); - } - - /// - /// Gets the status string for console command - /// - /// license status valid or invalid - protected override string GetStatusString() - { - return string.Format("License Status: {0}", IsValid ? "Valid" : "Not Valid"); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Lighting/Lighting Interfaces.cs b/src/PepperDash.Essentials.Core/Lighting/Lighting Interfaces.cs index dfc6cd28..642ca6f8 100644 --- a/src/PepperDash.Essentials.Core/Lighting/Lighting Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Lighting/Lighting Interfaces.cs @@ -1,118 +1,118 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core.Lighting; - - -/// -/// Defines the contract for ILightingScenes -/// -public interface ILightingScenes +namespace PepperDash.Essentials.Core.Lighting { /// - /// Fires when the lighting scene changes + /// Defines the contract for ILightingScenes /// - event EventHandler LightingSceneChange; - - /// - /// Gets the list of LightingScenes - /// - List LightingScenes { get; } - - /// - /// Selects the given LightingScene - /// - /// scene to select - void SelectScene(LightingScene scene); - - /// - /// Gets the currently selected LightingScene - /// - LightingScene CurrentLightingScene { get; } - -} - -/// -/// Defines the contract for ILightingScenesDynamic -/// -public interface ILightingScenesDynamic : ILightingScenes -{ - /// - /// Fires when the lighting scenes are updated - /// - event EventHandler LightingScenesUpdated; -} - -/// -/// Defines the contract for ILightingMasterRaiseLower -/// -public interface ILightingMasterRaiseLower -{ - /// - /// Raises the master level - /// - void MasterRaise(); - - /// - /// Lowers the master level - /// - void MasterLower(); - - /// - /// Stops raising or lowering the master level - /// - void MasterRaiseLowerStop(); -} - -/// -/// Defines the contract for ILightingLoad -/// -public interface ILightingLoad -{ - /// - /// Sets the load level - /// - /// level to set - void SetLoadLevel(int level); - - /// - /// Raises the load level - /// - void Raise(); - - /// - /// Lowers the load level - /// - void Lower(); - - /// - /// feedback of the current load level - /// - IntFeedback LoadLevelFeedback { get; } - - /// - /// feedback of whether the load is on - /// - BoolFeedback LoadIsOnFeedback { get; } -} - -/// -/// Represents a LightingSceneChangeEventArgs -/// -public class LightingSceneChangeEventArgs : EventArgs -{ - /// - /// Gets or sets the CurrentLightingScene - /// - public LightingScene CurrentLightingScene { get; private set; } - - /// - /// Constructor for LightingSceneChangeEventArgs - /// - /// The lighting scene that changed - public LightingSceneChangeEventArgs(LightingScene scene) + public interface ILightingScenes { - CurrentLightingScene = scene; - } -} + /// + /// Fires when the lighting scene changes + /// + event EventHandler LightingSceneChange; + /// + /// Gets the list of LightingScenes + /// + List LightingScenes { get; } + + /// + /// Selects the given LightingScene + /// + /// scene to select + void SelectScene(LightingScene scene); + + /// + /// Gets the currently selected LightingScene + /// + LightingScene CurrentLightingScene { get; } + + } + + /// + /// Defines the contract for ILightingScenesDynamic + /// + public interface ILightingScenesDynamic : ILightingScenes + { + /// + /// Fires when the lighting scenes are updated + /// + event EventHandler LightingScenesUpdated; + } + + /// + /// Defines the contract for ILightingMasterRaiseLower + /// + public interface ILightingMasterRaiseLower + { + /// + /// Raises the master level + /// + void MasterRaise(); + + /// + /// Lowers the master level + /// + void MasterLower(); + + /// + /// Stops raising or lowering the master level + /// + void MasterRaiseLowerStop(); + } + + /// + /// Defines the contract for ILightingLoad + /// + public interface ILightingLoad + { + /// + /// Sets the load level + /// + /// level to set + void SetLoadLevel(int level); + + /// + /// Raises the load level + /// + void Raise(); + + /// + /// Lowers the load level + /// + void Lower(); + + /// + /// feedback of the current load level + /// + IntFeedback LoadLevelFeedback { get; } + + /// + /// feedback of whether the load is on + /// + BoolFeedback LoadIsOnFeedback { get; } + } + + /// + /// Represents a LightingSceneChangeEventArgs + /// + public class LightingSceneChangeEventArgs : EventArgs + { + /// + /// Gets or sets the CurrentLightingScene + /// + public LightingScene CurrentLightingScene { get; private set; } + + /// + /// Constructor for LightingSceneChangeEventArgs + /// + /// The lighting scene that changed + public LightingSceneChangeEventArgs(LightingScene scene) + { + CurrentLightingScene = scene; + } + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs b/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs index fd704f76..a15e0dc4 100644 --- a/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs +++ b/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs @@ -11,272 +11,286 @@ using PepperDash.Essentials.Core.CrestronIO; using Serilog.Events; -namespace PepperDash.Essentials.Core.Privacy; - -/// -/// Used for applications where one or more microphones with momentary contact closure outputs are used to -/// toggle the privacy state of the room. Privacy state feedback is represented -/// -public class MicrophonePrivacyController : EssentialsDevice +namespace PepperDash.Essentials.Core.Privacy { - MicrophonePrivacyControllerConfig Config; + /// + /// Used for applications where one or more microphones with momentary contact closure outputs are used to + /// toggle the privacy state of the room. Privacy state feedback is represented + /// + public class MicrophonePrivacyController : EssentialsDevice + { + MicrophonePrivacyControllerConfig Config; - bool initialized; + bool initialized; /// /// Gets or sets whether LED control is enabled /// - public bool EnableLeds - { - get + public bool EnableLeds { - return _enableLeds; - } - set - { - _enableLeds = value; - - if (initialized) + get { - if (value) + return _enableLeds; + } + set + { + _enableLeds = value; + + if (initialized) { - CheckPrivacyMode(); - SetLedStates(); + if (value) + { + CheckPrivacyMode(); + SetLedStates(); + } + else + TurnOffAllLeds(); } + } + } + bool _enableLeds; + + /// + /// Gets or sets the Inputs + /// + public List Inputs { get; private set; } + + /// + /// Gets or sets the RedLedRelay + /// + public GenericRelayDevice RedLedRelay { get; private set; } + bool _redLedRelayState; + + /// + /// Gets or sets the GreenLedRelay + /// + public GenericRelayDevice GreenLedRelay { get; private set; } + bool _greenLedRelayState; + + /// + /// Gets or sets the PrivacyDevice + /// + public IPrivacy PrivacyDevice { get; private set; } + + /// + /// Constructor for MicrophonePrivacyController + /// + /// key of the controller device + /// configuration for the controller device + public MicrophonePrivacyController(string key, MicrophonePrivacyControllerConfig config) : + base(key) + { + Config = config; + + Inputs = new List(); + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + foreach (var i in Config.Inputs) + { + var input = DeviceManager.GetDeviceForKey(i.DeviceKey) as IDigitalInput; + + if(input != null) + AddInput(input); + } + + var greenLed = DeviceManager.GetDeviceForKey(Config.GreenLedRelay.DeviceKey) as GenericRelayDevice; + + if (greenLed != null) + GreenLedRelay = greenLed; + else + Debug.LogMessage(LogEventLevel.Information, this, "Unable to add Green LED device"); + + var redLed = DeviceManager.GetDeviceForKey(Config.RedLedRelay.DeviceKey) as GenericRelayDevice; + + if (redLed != null) + RedLedRelay = redLed; + else + Debug.LogMessage(LogEventLevel.Information, this, "Unable to add Red LED device"); + + AddPostActivationAction(() => { + PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange -= PrivacyModeIsOnFeedback_OutputChange; + PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange; + }); + + initialized = true; + + return base.CustomActivate(); + } + + #region Overrides of Device + + /// + /// Initialize method + /// + /// + public override void Initialize() + { + CheckPrivacyMode(); + } + + #endregion + + /// + /// SetPrivacyDevice method + /// + public void SetPrivacyDevice(IPrivacy privacyDevice) + { + PrivacyDevice = privacyDevice; + } + + void PrivacyModeIsOnFeedback_OutputChange(object sender, EventArgs e) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Privacy mode change: {0}", sender as BoolFeedback); + CheckPrivacyMode(); + } + + void CheckPrivacyMode() + { + if (PrivacyDevice != null) + { + var privacyState = PrivacyDevice.PrivacyModeIsOnFeedback.BoolValue; + + if (privacyState) + TurnOnRedLeds(); else - TurnOffAllLeds(); + TurnOnGreenLeds(); + } + } + + void AddInput(IDigitalInput input) + { + Inputs.Add(input); + + input.InputStateFeedback.OutputChange += InputStateFeedback_OutputChange; + } + + void RemoveInput(IDigitalInput input) + { + var tempInput = Inputs.FirstOrDefault(i => i.Equals(input)); + + if (tempInput != null) + tempInput.InputStateFeedback.OutputChange -= InputStateFeedback_OutputChange; + + Inputs.Remove(input); + } + + void SetRedLedRelay(GenericRelayDevice relay) + { + RedLedRelay = relay; + } + + void SetGreenLedRelay(GenericRelayDevice relay) + { + GreenLedRelay = relay; + } + + /// + /// Check the state of the input change and handle accordingly + /// + /// + /// + void InputStateFeedback_OutputChange(object sender, EventArgs e) + { + if ((sender as BoolFeedback).BoolValue == true) + TogglePrivacyMute(); + } + + /// + /// Toggles the state of the privacy mute + /// + public void TogglePrivacyMute() + { + PrivacyDevice.PrivacyModeToggle(); + } + + void TurnOnRedLeds() + { + _greenLedRelayState = false; + _redLedRelayState = true; + SetLedStates(); + } + + void TurnOnGreenLeds() + { + _redLedRelayState = false; + _greenLedRelayState = true; + SetLedStates(); + } + + /// + /// If enabled, sets the actual state of the relays + /// + void SetLedStates() + { + if (_enableLeds) + { + SetRelayStates(); + } + else + TurnOffAllLeds(); + } + + /// + /// Turns off all LEDs + /// + void TurnOffAllLeds() + { + _redLedRelayState = false; + _greenLedRelayState = false; + + SetRelayStates(); + } + + void SetRelayStates() + { + if (RedLedRelay != null) + { + if (_redLedRelayState) + RedLedRelay.CloseRelay(); + else + RedLedRelay.OpenRelay(); + } + + if(GreenLedRelay != null) + { + if (_greenLedRelayState) + GreenLedRelay.CloseRelay(); + else + GreenLedRelay.OpenRelay(); } } } - - bool _enableLeds; /// - /// Gets or sets the list of digital inputs that are used to toggle the privacy state. Each input is expected to be momentary contact closure that triggers a change in the privacy state when activated. The controller will subscribe to the OutputChange event of each input's InputStateFeedback to monitor for changes in the input state and respond accordingly by toggling the privacy state and updating the LED indicators if enabled. + /// Represents a MicrophonePrivacyControllerFactory /// - public List Inputs { get; private set; } - - /// - /// Gets or sets the GenericRelayDevice that is used to indicate the privacy state with a red LED. When the privacy mode is active, the red LED will be turned on to provide a visual indication of the privacy state. The controller will manage the state of this relay based on changes in the privacy mode, ensuring that it accurately reflects whether the privacy mode is currently active or not. - /// - public GenericRelayDevice RedLedRelay { get; private set; } - bool _redLedRelayState; - - /// - /// Gets or sets the GenericRelayDevice that is used to indicate the privacy state with a green LED. When the privacy mode is inactive, the green LED will be turned on to provide a visual indication of the privacy state. The controller will manage the state of this relay based on changes in the privacy mode, ensuring that it accurately reflects whether the privacy mode is currently active or not. - /// - public GenericRelayDevice GreenLedRelay { get; private set; } - bool _greenLedRelayState; - - /// - public IPrivacy PrivacyDevice { get; private set; } - - /// - /// Constructor for the MicrophonePrivacyController class, which initializes the controller with the specified key and configuration. The constructor sets up the necessary properties and collections for managing the digital inputs, LED relays, and privacy device. It also prepares the controller for activation by ensuring that all required components are properly initialized and ready to be used when the controller is activated in the system. - /// - /// - /// - public MicrophonePrivacyController(string key, MicrophonePrivacyControllerConfig config) : - base(key) + public class MicrophonePrivacyControllerFactory : EssentialsDeviceFactory { - Config = config; - - Inputs = new List(); - } - - protected override bool CustomActivate() - { - foreach (var i in Config.Inputs) + /// + /// Constructor for MicrophonePrivacyControllerFactory + /// + public MicrophonePrivacyControllerFactory() { - var input = DeviceManager.GetDeviceForKey(i.DeviceKey) as IDigitalInput; - - if(input != null) - AddInput(input); + TypeNames = new List() { "microphoneprivacycontroller" }; } - var greenLed = DeviceManager.GetDeviceForKey(Config.GreenLedRelay.DeviceKey) as GenericRelayDevice; - - if (greenLed != null) - GreenLedRelay = greenLed; - else - Debug.LogMessage(LogEventLevel.Information, this, "Unable to add Green LED device"); - - var redLed = DeviceManager.GetDeviceForKey(Config.RedLedRelay.DeviceKey) as GenericRelayDevice; - - if (redLed != null) - RedLedRelay = redLed; - else - Debug.LogMessage(LogEventLevel.Information, this, "Unable to add Red LED device"); - - AddPostActivationAction(() => { - PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange -= PrivacyModeIsOnFeedback_OutputChange; - PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange; - }); - - initialized = true; - - return base.CustomActivate(); - } - - #region Overrides of Device - - /// - protected override void Initialize() - { - CheckPrivacyMode(); - } - - #endregion - - /// - public void SetPrivacyDevice(IPrivacy privacyDevice) - { - PrivacyDevice = privacyDevice; - } - - void PrivacyModeIsOnFeedback_OutputChange(object sender, EventArgs e) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Privacy mode change: {0}", sender as BoolFeedback); - CheckPrivacyMode(); - } - - void CheckPrivacyMode() - { - if (PrivacyDevice != null) + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) { - var privacyState = PrivacyDevice.PrivacyModeIsOnFeedback.BoolValue; + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MIcrophonePrivacyController Device"); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - if (privacyState) - TurnOnRedLeds(); - else - TurnOnGreenLeds(); + return new Core.Privacy.MicrophonePrivacyController(dc.Key, props); } } - void AddInput(IDigitalInput input) - { - Inputs.Add(input); - - input.InputStateFeedback.OutputChange += InputStateFeedback_OutputChange; - } - - void RemoveInput(IDigitalInput input) - { - var tempInput = Inputs.FirstOrDefault(i => i.Equals(input)); - - if (tempInput != null) - tempInput.InputStateFeedback.OutputChange -= InputStateFeedback_OutputChange; - - Inputs.Remove(input); - } - - void SetRedLedRelay(GenericRelayDevice relay) - { - RedLedRelay = relay; - } - - void SetGreenLedRelay(GenericRelayDevice relay) - { - GreenLedRelay = relay; - } - - /// - /// Check the state of the input change and handle accordingly - /// - /// - /// - void InputStateFeedback_OutputChange(object sender, EventArgs e) - { - if ((sender as BoolFeedback).BoolValue == true) - TogglePrivacyMute(); - } - - /// - /// Toggles the state of the privacy mute - /// - public void TogglePrivacyMute() - { - PrivacyDevice.PrivacyModeToggle(); - } - - void TurnOnRedLeds() - { - _greenLedRelayState = false; - _redLedRelayState = true; - SetLedStates(); - } - - void TurnOnGreenLeds() - { - _redLedRelayState = false; - _greenLedRelayState = true; - SetLedStates(); - } - - /// - /// If enabled, sets the actual state of the relays - /// - void SetLedStates() - { - if (_enableLeds) - { - SetRelayStates(); - } - else - TurnOffAllLeds(); - } - - /// - /// Turns off all LEDs - /// - void TurnOffAllLeds() - { - _redLedRelayState = false; - _greenLedRelayState = false; - - SetRelayStates(); - } - - void SetRelayStates() - { - if (RedLedRelay != null) - { - if (_redLedRelayState) - RedLedRelay.CloseRelay(); - else - RedLedRelay.OpenRelay(); - } - - if(GreenLedRelay != null) - { - if (_greenLedRelayState) - GreenLedRelay.CloseRelay(); - else - GreenLedRelay.OpenRelay(); - } - } -} - -/// -/// Factory for creating MicrophonePrivacyController devices -/// -public class MicrophonePrivacyControllerFactory : EssentialsDeviceFactory -{ - /// - /// Constructor for the MicrophonePrivacyControllerFactory class, which initializes the factory and sets up the necessary type names for device creation. This factory is responsible for creating instances of the MicrophonePrivacyController device based on the provided configuration and device key when requested by the system. - /// - public MicrophonePrivacyControllerFactory() - { - TypeNames = new List() { "microphoneprivacycontroller" }; - } - - - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MIcrophonePrivacyController Device"); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - - return new Core.Privacy.MicrophonePrivacyController(dc.Key, props); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyControllerConfig.cs b/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyControllerConfig.cs index d5df4531..902b51ed 100644 --- a/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyControllerConfig.cs +++ b/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyControllerConfig.cs @@ -1,4 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Essentials.Core.CrestronIO; namespace PepperDash.Essentials.Core.Privacy { diff --git a/src/PepperDash.Essentials.Core/Monitoring/CrestronGenericBaseCommunicationMonitor.cs b/src/PepperDash.Essentials.Core/Monitoring/CrestronGenericBaseCommunicationMonitor.cs index 2fce0783..dc9406a3 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/CrestronGenericBaseCommunicationMonitor.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/CrestronGenericBaseCommunicationMonitor.cs @@ -11,8 +11,8 @@ using System.ComponentModel; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// /// @@ -68,4 +68,5 @@ namespace PepperDash.Essentials.Core; else StartErrorTimers(); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Monitoring/GenericCommunicationMonitor.cs b/src/PepperDash.Essentials.Core/Monitoring/GenericCommunicationMonitor.cs index 350ad893..34f074d9 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/GenericCommunicationMonitor.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/GenericCommunicationMonitor.cs @@ -3,319 +3,321 @@ using PepperDash.Core; using System.Threading; using PepperDash.Core.Logging; -namespace PepperDash.Essentials.Core; - -/// -/// Used for monitoring comms that are IBasicCommunication. Will send a poll string and provide an event when -/// statuses change. -/// Default monitoring uses TextReceived event on Client. -/// -public class GenericCommunicationMonitor : StatusMonitorBase +namespace PepperDash.Essentials.Core { - /// - /// Gets the Client being monitored - /// - public IBasicCommunication Client { get; private set; } + /// + /// Used for monitoring comms that are IBasicCommunication. Will send a poll string and provide an event when + /// statuses change. + /// Default monitoring uses TextReceived event on Client. + /// + public class GenericCommunicationMonitor : StatusMonitorBase + { + /// + /// Gets the Client being monitored + /// + public IBasicCommunication Client { get; private set; } - /// - /// Will monitor Client.BytesReceived if set to true. Otherwise the default is to monitor Client.TextReceived - /// - public bool MonitorBytesReceived { get; private set; } + /// + /// Will monitor Client.BytesReceived if set to true. Otherwise the default is to monitor Client.TextReceived + /// + public bool MonitorBytesReceived { get; private set; } - /// - /// Return true if the Client is ISocketStatus - /// - public bool IsSocket => Client is ISocketStatus; + /// + /// Return true if the Client is ISocketStatus + /// + public bool IsSocket => Client is ISocketStatus; - private readonly string PollString; - private readonly Action PollAction; - private readonly long PollTime; + private readonly string PollString; + private readonly Action PollAction; + private readonly long PollTime; - private Timer PollTimer; + private Timer PollTimer; - private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); + private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); - /// - /// GenericCommunicationMonitor constructor - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent device - /// Communications Client - /// Time in MS for polling - /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning - /// Error time in MS. If a message is not received before this elapsed time the status will be Error - /// string to send for polling - /// Poll time must be less than warning and error time - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, - long warningTime, long errorTime, string pollString) : - base(parent, warningTime, errorTime) - { - if (pollTime > warningTime || pollTime > errorTime) - throw new ArgumentException("pollTime must be less than warning or errorTime"); + /// + /// GenericCommunicationMonitor constructor + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent device + /// Communications Client + /// Time in MS for polling + /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning + /// Error time in MS. If a message is not received before this elapsed time the status will be Error + /// string to send for polling + /// Poll time must be less than warning and error time + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, string pollString) : + base(parent, warningTime, errorTime) + { + if (pollTime > warningTime || pollTime > errorTime) + throw new ArgumentException("pollTime must be less than warning or errorTime"); - Client = client; - PollTime = pollTime; - PollString = pollString; + Client = client; + PollTime = pollTime; + PollString = pollString; - if (IsSocket) - { - (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; - } - } - - /// - /// GenericCommunicationMonitor constructor with a bool to specify whether to monitor BytesReceived - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent device - /// Communications Client - /// Time in MS for polling - /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning - /// Error time in MS. If a message is not received before this elapsed time the status will be Error - /// string to send for polling - /// Use bytesReceived event instead of textReceived when true - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, - long warningTime, long errorTime, string pollString, bool monitorBytesReceived) : - this(parent, client, pollTime, warningTime, errorTime, pollString) - { - MonitorBytesReceived = monitorBytesReceived; - } - - /// - /// GenericCommunicationMonitor constructor with a poll action instead of a poll string - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent device - /// Communications Client - /// Time in MS for polling - /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning - /// Error time in MS. If a message is not received before this elapsed time the status will be Error - /// Action to execute for polling - /// Poll time must be less than warning and error time - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, - long warningTime, long errorTime, Action pollAction) : - base(parent, warningTime, errorTime) - { - if (pollTime > warningTime || pollTime > errorTime) - throw new ArgumentException("pollTime must be less than warning or errorTime"); - //if (pollTime < 5000) - // throw new ArgumentException("pollTime cannot be less than 5000 ms"); - - Client = client; - PollTime = pollTime; - PollAction = pollAction; - - if (IsSocket) - { - (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; - } - - } - - /// - /// GenericCommunicationMonitor constructor with a poll action instead of a poll string and a bool to specify whether to monitor BytesReceived - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent device - /// Communications Client - /// Time in MS for polling - /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning - /// Error time in MS. If a message is not received before this elapsed time the status will be Error - /// Action to execute for polling - /// Use bytesReceived event instead of textReceived when true - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, - long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) : - this(parent, client, pollTime, warningTime, errorTime, pollAction) - { - MonitorBytesReceived = monitorBytesReceived; - } - - - /// - /// GenericCommunicationMonitor constructor with a config object - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent Device - /// Communications Client - /// Communication Monitor Config object - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, - CommunicationMonitorConfig props) : - this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) - { - if (IsSocket) - { - (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; - } - } - - /// - /// GenericCommunicationMonitor constructor with a config object and a bool to specify whether to monitor BytesReceived - /// - /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected - /// - /// Parent Device - /// Communications Client - /// Communication Monitor Config object - /// Use bytesReceived event instead of textReceived when true - public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) : - this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) - { - MonitorBytesReceived = monitorBytesReceived; - } - - /// - /// Start the poll cycle - /// - public override void Start() - { - if (MonitorBytesReceived) - { - Client.BytesReceived -= Client_BytesReceived; - Client.BytesReceived += Client_BytesReceived; - } - else - { - Client.TextReceived -= Client_TextReceived; - Client.TextReceived += Client_TextReceived; - } - - BeginPolling(); - } - - private void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) - { - if (!e.Client.IsConnected) - { - // Immediately stop polling and notify that device is offline - Stop(); - Status = MonitorStatus.InError; - ResetErrorTimers(); - } - else - { - // Start polling and set status to unknow and let poll result update the status to IsOk when a response is received - Status = MonitorStatus.StatusUnknown; - Start(); - } - } - - private void BeginPolling() - { - try - { - semaphore.Wait(); + if (IsSocket) { - if (PollTimer != null) - { - return; - } + (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; + } + } - PollTimer = new Timer(o => Poll(), null, 0, PollTime); + /// + /// GenericCommunicationMonitor constructor with a bool to specify whether to monitor BytesReceived + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent device + /// Communications Client + /// Time in MS for polling + /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning + /// Error time in MS. If a message is not received before this elapsed time the status will be Error + /// string to send for polling + /// Use bytesReceived event instead of textReceived when true + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, string pollString, bool monitorBytesReceived) : + this(parent, client, pollTime, warningTime, errorTime, pollString) + { + MonitorBytesReceived = monitorBytesReceived; + } + + /// + /// GenericCommunicationMonitor constructor with a poll action instead of a poll string + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent device + /// Communications Client + /// Time in MS for polling + /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning + /// Error time in MS. If a message is not received before this elapsed time the status will be Error + /// Action to execute for polling + /// Poll time must be less than warning and error time + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, Action pollAction) : + base(parent, warningTime, errorTime) + { + if (pollTime > warningTime || pollTime > errorTime) + throw new ArgumentException("pollTime must be less than warning or errorTime"); + //if (pollTime < 5000) + // throw new ArgumentException("pollTime cannot be less than 5000 ms"); + + Client = client; + PollTime = pollTime; + PollAction = pollAction; + + if (IsSocket) + { + (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; + } + + } + + /// + /// GenericCommunicationMonitor constructor with a poll action instead of a poll string and a bool to specify whether to monitor BytesReceived + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent device + /// Communications Client + /// Time in MS for polling + /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning + /// Error time in MS. If a message is not received before this elapsed time the status will be Error + /// Action to execute for polling + /// Use bytesReceived event instead of textReceived when true + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, + long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) : + this(parent, client, pollTime, warningTime, errorTime, pollAction) + { + MonitorBytesReceived = monitorBytesReceived; + } + + + /// + /// GenericCommunicationMonitor constructor with a config object + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent Device + /// Communications Client + /// Communication Monitor Config object + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, + CommunicationMonitorConfig props) : + this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) + { + if (IsSocket) + { + (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; + } + } + + /// + /// GenericCommunicationMonitor constructor with a config object and a bool to specify whether to monitor BytesReceived + /// + /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected + /// + /// Parent Device + /// Communications Client + /// Communication Monitor Config object + /// Use bytesReceived event instead of textReceived when true + public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) : + this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) + { + MonitorBytesReceived = monitorBytesReceived; + } + + /// + /// Start the poll cycle + /// + public override void Start() + { + if (MonitorBytesReceived) + { + Client.BytesReceived -= Client_BytesReceived; + Client.BytesReceived += Client_BytesReceived; + } + else + { + Client.TextReceived -= Client_TextReceived; + Client.TextReceived += Client_TextReceived; + } + + BeginPolling(); + } + + private void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) + { + if (!e.Client.IsConnected) + { + // Immediately stop polling and notify that device is offline + Stop(); + Status = MonitorStatus.InError; + ResetErrorTimers(); + } + else + { + // Start polling and set status to unknow and let poll result update the status to IsOk when a response is received + Status = MonitorStatus.StatusUnknown; + Start(); } } - finally - { - semaphore.Release(); - } - } - /// - /// Stop the poll cycle - /// - public override void Stop() - { - if (MonitorBytesReceived) + private void BeginPolling() { - Client.BytesReceived -= Client_BytesReceived; - } - else - { - Client.TextReceived -= Client_TextReceived; + try + { + semaphore.Wait(); + { + if (PollTimer != null) + { + return; + } + + PollTimer = new Timer(o => Poll(), null, 0, PollTime); + } + } + finally + { + semaphore.Release(); + } } - StopErrorTimers(); - - if (PollTimer == null) - { - return; - } - - PollTimer.Dispose(); - PollTimer = null; - } - - private void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) - { - DataReceived(); - } - - private void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) - { - DataReceived(); - } - - private void DataReceived() - { - Status = MonitorStatus.IsOk; - ResetErrorTimers(); - } - - private void Poll() - { - StartErrorTimers(); - if (Client.IsConnected) - { - //Debug.LogMessage(LogEventLevel.Verbose, this, "Polling"); - if (PollAction != null) - PollAction.Invoke(); + /// + /// Stop method + /// + /// + public override void Stop() + { + if(MonitorBytesReceived) + { + Client.BytesReceived -= Client_BytesReceived; + } else - Client.SendText(PollString); - } - else + { + Client.TextReceived -= Client_TextReceived; + } + + StopErrorTimers(); + + if (PollTimer == null) + { + return; + } + + PollTimer.Dispose(); + PollTimer = null; + } + + private void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) { - this.LogVerbose("Comm not connected"); + DataReceived(); } - } -} -/// -/// Communication Monitor Configuration from Essentials Configuration -/// -public class CommunicationMonitorConfig -{ - /// - /// Gets or sets the PollInterval - /// - public int PollInterval { get; set; } + private void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) + { + DataReceived(); + } + + private void DataReceived() + { + Status = MonitorStatus.IsOk; + ResetErrorTimers(); + } + + private void Poll() + { + StartErrorTimers(); + if (Client.IsConnected) + { + //Debug.LogMessage(LogEventLevel.Verbose, this, "Polling"); + if(PollAction != null) + PollAction.Invoke(); + else + Client.SendText(PollString); + } + else + { + this.LogVerbose("Comm not connected"); + } + } + } /// - /// Gets or sets the TimeToWarning + /// Represents a CommunicationMonitorConfig /// - public int TimeToWarning { get; set; } + public class CommunicationMonitorConfig + { + /// + /// Gets or sets the PollInterval + /// + public int PollInterval { get; set; } - /// - /// Gets or sets the TimeToError - /// - public int TimeToError { get; set; } + /// + /// Gets or sets the TimeToWarning + /// + public int TimeToWarning { get; set; } - /// - /// Gets or sets the PollString - /// - public string PollString { get; set; } + /// + /// Gets or sets the TimeToError + /// + public int TimeToError { get; set; } - /// - /// Default constructor. Sets pollInterval to 30s, TimeToWarning to 120s, and TimeToError to 300s - /// - public CommunicationMonitorConfig() - { - PollInterval = 30000; - TimeToWarning = 120000; - TimeToError = 300000; - PollString = ""; - } + /// + /// Gets or sets the PollString + /// + public string PollString { get; set; } + + /// + /// Default constructor. Sets pollInterval to 30s, TimeToWarning to 120s, and TimeToError to 300s + /// + public CommunicationMonitorConfig() + { + PollInterval = 30000; + TimeToWarning = 120000; + TimeToError = 300000; + PollString = ""; + } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs index cf6962b6..7f44fa0f 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorBase.cs @@ -7,7 +7,6 @@ using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using System.ComponentModel; -using System.Timers; using PepperDash.Core; @@ -24,15 +23,15 @@ namespace PepperDash.Essentials.Core /// public event EventHandler StatusChange; - /// - /// Gets or sets the Key - /// - public string Key { get { return Parent.Key + "-comMonitor"; } } + /// + /// Gets or sets the Key + /// + public string Key { get { return Parent.Key + "-comMonitor"; } } - /// - /// Gets or sets the Name - /// - public string Name { get { return "Comm. monitor"; } } + /// + /// Gets or sets the Name + /// + public string Name { get { return "Comm. monitor"; } } /// /// Gets or sets the Parent @@ -60,7 +59,7 @@ namespace PepperDash.Essentials.Core if (value != _Status) { _Status = value; - + OnStatusChange(value); } } @@ -73,28 +72,22 @@ namespace PepperDash.Essentials.Core public string Message { get { return _Message; } - set + set { if (value == _Message) return; _Message = value; OnStatusChange(Status, value); - + } } string _Message; long WarningTime; long ErrorTime; - Timer WarningTimer; - Timer ErrorTimer; + CTimer WarningTimer; + CTimer ErrorTimer; - // Guards WarningTimer/ErrorTimer against concurrent access - StartErrorTimers/StopErrorTimers/ - // ResetErrorTimers can each be invoked from different threads (e.g. a socket data-received - // callback vs. a connection-status-changed callback), and without this lock a null-check followed - // by a dereference (e.g. in ResetErrorTimers) can race with StopErrorTimers nulling the field in - // between, throwing an unhandled NullReferenceException on a background thread that crashes the - // whole program. - private readonly object _timerLock = new object(); + /// /// Constructor /// /// parent device @@ -162,21 +155,8 @@ namespace PepperDash.Essentials.Core /// protected void StartErrorTimers() { - lock (_timerLock) - { - if (WarningTimer == null) - { - WarningTimer = new Timer(WarningTime) { AutoReset = false }; - WarningTimer.Elapsed += (s, e) => { Status = MonitorStatus.InWarning; }; - WarningTimer.Start(); - } - if (ErrorTimer == null) - { - ErrorTimer = new Timer(ErrorTime) { AutoReset = false }; - ErrorTimer.Elapsed += (s, e) => { Status = MonitorStatus.InError; }; - ErrorTimer.Start(); - } - } + if (WarningTimer == null) WarningTimer = new CTimer(o => { Status = MonitorStatus.InWarning; }, WarningTime); + if (ErrorTimer == null) ErrorTimer = new CTimer(o => { Status = MonitorStatus.InError; }, ErrorTime); } /// @@ -184,35 +164,22 @@ namespace PepperDash.Essentials.Core /// protected void StopErrorTimers() { - lock (_timerLock) - { - if (WarningTimer != null) WarningTimer.Stop(); - if (ErrorTimer != null) ErrorTimer.Stop(); - WarningTimer = null; - ErrorTimer = null; - } + if (WarningTimer != null) WarningTimer.Stop(); + if (ErrorTimer != null) ErrorTimer.Stop(); + WarningTimer = null; + ErrorTimer = null; } /// /// Resets the error timers /// - protected void ResetErrorTimers() - { - lock (_timerLock) - { - if (WarningTimer != null) - { - WarningTimer.Stop(); - WarningTimer.Interval = WarningTime; - WarningTimer.Start(); - } - if (ErrorTimer != null) - { - ErrorTimer.Stop(); - ErrorTimer.Interval = ErrorTime; - ErrorTimer.Start(); - } - } - } + protected void ResetErrorTimers() + { + if(WarningTimer != null) + WarningTimer.Reset(WarningTime, WarningTime); + if(ErrorTimer != null) + ErrorTimer.Reset(ErrorTime, ErrorTime); + + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorCollection.cs b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorCollection.cs index a6aa6c3d..58aba6a1 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorCollection.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/StatusMonitorCollection.cs @@ -11,8 +11,8 @@ using System.ComponentModel; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Collection class for multiple status monitors /// @@ -166,4 +166,5 @@ namespace PepperDash.Essentials.Core; if (handler != null) handler(this, new MonitorStatusChangeEventArgs(status, message)); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs b/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs index 295438e7..61e70117 100644 --- a/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs +++ b/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs @@ -1,4 +1,6 @@ -using System; + + +using System; using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp; @@ -9,1127 +11,1065 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PepperDash.Essentials.Core.Bridges; using Serilog.Events; -using System.Threading.Tasks; -using System.Timers; -namespace PepperDash.Essentials.Core.Monitoring; - -/// -/// Wrapper for the static SystemMonitor class to extend functionality and provide external access -/// to SystemMonitor via APIs -/// -public class SystemMonitorController : EssentialsBridgeableDevice +namespace PepperDash.Essentials.Core.Monitoring { - private const long UptimePollTime = 300000; - private Timer _uptimePollTimer; - - private string _uptime; - private string _lastStart; - /// - /// Event fired when any SystemMonitor property changes + /// Wrapper for the static SystemMonitor class to extend functionality and provide external access + /// to SystemMonitor via APIs /// - public event EventHandler SystemMonitorPropertiesChanged; - - /// - /// Gets or sets the ProgramStatusFeedbackCollection - /// - public Dictionary ProgramStatusFeedbackCollection; - - /// - /// Gets or sets the EthernetStatusFeedbackCollection - /// - public Dictionary EthernetStatusFeedbackCollection; - - /// - /// Gets or sets the TimeZoneFeedback - /// - public IntFeedback TimeZoneFeedback { get; protected set; } - - /// - /// Gets or sets the TimeZoneTextFeedback - /// - public StringFeedback TimeZoneTextFeedback { get; protected set; } - - /// - /// Gets or sets the IoControllerVersionFeedback - /// - public StringFeedback IoControllerVersionFeedback { get; protected set; } - - /// - /// Gets or sets the SnmpVersionFeedback - /// - public StringFeedback SnmpVersionFeedback { get; protected set; } - - /// - /// Gets or sets the BaCnetAppVersionFeedback - /// - public StringFeedback BaCnetAppVersionFeedback { get; protected set; } - - /// - /// Gets or sets the ControllerVersionFeedback - /// - public StringFeedback ControllerVersionFeedback { get; protected set; } - - //new feedbacks. Issue #50 - /// - /// Gets or sets the SerialNumberFeedback - /// - public StringFeedback SerialNumberFeedback { get; protected set; } - - /// - /// Gets or sets the ModelFeedback - /// - public StringFeedback ModelFeedback { get; set; } - - /// - /// Gets or sets the UptimeFeedback - /// - public StringFeedback UptimeFeedback { get; set; } - - /// - /// Gets or sets the LastStartFeedback - /// - public StringFeedback LastStartFeedback { get; set; } - - /// - /// Gets or sets the IsApplianceFeedback - /// - public BoolFeedback IsApplianceFeedback { get; protected set; } - private bool _isApplianceFb + public class SystemMonitorController : EssentialsBridgeableDevice { - get { return CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; } - } + private const long UptimePollTime = 300000; + private CTimer _uptimePollTimer; - /// - /// Gets or sets the IsServerFeedback - /// - public BoolFeedback IsServerFeedback { get; protected set; } - private bool _isServerFb - { - get { return CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; } - } - - /// - /// Constructor - /// - /// device key - public SystemMonitorController(string key) - : base(key) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Adding SystemMonitorController."); - - SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; - - TimeZoneFeedback = new IntFeedback(() => SystemMonitor.TimeZoneInformation.TimeZoneNumber); - TimeZoneTextFeedback = new StringFeedback(() => SystemMonitor.TimeZoneInformation.TimeZoneName); - - IoControllerVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.IOPVersion); - SnmpVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.SNMPVersion); - BaCnetAppVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.BACNetVersion); - ControllerVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.ControlSystemVersion); - - SerialNumberFeedback = new StringFeedback(() => CrestronEnvironment.SystemInfo.SerialNumber); - ModelFeedback = new StringFeedback(() => InitialParametersClass.ControllerPromptName); - UptimeFeedback = new StringFeedback(() => _uptime); - LastStartFeedback = new StringFeedback(() => _lastStart); - - IsApplianceFeedback = new BoolFeedback(() => _isApplianceFb); - IsServerFeedback = new BoolFeedback(() => _isServerFb); - - ProgramStatusFeedbackCollection = new Dictionary(); - - foreach (var prog in SystemMonitor.ProgramCollection) - { - var program = new ProgramStatusFeedbacks(prog); - ProgramStatusFeedbackCollection.Add(prog.Number, program); - } - - CreateEthernetStatusFeedbacks(); - UpdateEthernetStatusFeeedbacks(); - - _uptimePollTimer = new Timer(UptimePollTime) { AutoReset = true }; - _uptimePollTimer.Elapsed += (s, e) => PollUptime(null); - _uptimePollTimer.Start(); - PollUptime(null); - - SystemMonitor.ProgramChange += SystemMonitor_ProgramChange; - SystemMonitor.TimeZoneInformation.TimeZoneChange += TimeZoneInformation_TimeZoneChange; - CrestronEnvironment.EthernetEventHandler += CrestronEnvironmentOnEthernetEventHandler; - CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironmentOnProgramStatusEventHandler; - } - - private void CrestronEnvironmentOnProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType != eProgramStatusEventType.Stopping) return; - - _uptimePollTimer.Stop(); - _uptimePollTimer.Dispose(); - _uptimePollTimer = null; - } - - /// - /// Polls the uptime and last start time from the control system and updates the feedbacks - /// This is necessary because there is no event that is fired when these values change, so we have to poll for them - /// at a regular interval - /// Uptime is also polled on activation to get initial values - /// Uptime is polled every 5 minutes (300000 ms) which should be often enough to keep the values updated without causing performance issues - /// Note: polling uptime can cause a delay in the feedback update, so it is done in a separate thread to avoid blocking the main thread - /// Note: this method uses CrestronConsole.SendControlSystemCommand which can be slow, so it is not recommended to call this method more often than every 5 minutes - /// Note: this method will not work on a server as the "uptime" command is not available, but it will not cause any issues either as it will just return an empty string - /// Note: if you need more real-time uptime updates, you could consider implementing a custom solution that tracks uptime internally and updates the feedbacks accordingly, but this would require more complex implementation and testing - /// Note: if you implement a custom solution for tracking uptime, you should still consider polling the uptime from the control system at a regular interval (e.g. every hour) to ensure that your internal tracking is accurate and to account for any potential issues that may arise with your custom implementation - /// Note: if you implement a custom solution for tracking uptime, you should also consider implementing a way to reset the uptime (e.g. on program start) to ensure that it reflects the actual uptime of the control system accurately - /// - /// - public void PollUptime(object obj) - { - var consoleResponse = string.Empty; - - CrestronConsole.SendControlSystemCommand("uptime", ref consoleResponse); - - ParseUptime(consoleResponse); - - UptimeFeedback.FireUpdate(); - LastStartFeedback.FireUpdate(); - } - - private void ParseUptime(string response) - { - var splitString = response.Trim().Split('\r', '\n'); - - var lastStartRaw = splitString.FirstOrDefault(o => o.Contains("started")); - var uptimeRaw = splitString.FirstOrDefault(o => o.Contains("running")); - - if (!String.IsNullOrEmpty(lastStartRaw)) - { - var lastStartIndex = lastStartRaw.IndexOf(':'); - _lastStart = lastStartRaw.Substring(lastStartIndex + 1).Trim(); - } - - if (String.IsNullOrEmpty(uptimeRaw)) return; - var forIndex = uptimeRaw.IndexOf("for", StringComparison.Ordinal); - - //4 => "for " to get what's on the right - _uptime = uptimeRaw.Substring(forIndex + 4); - } - - /// - /// ProcessorReboot method - /// - public static void ProcessorReboot() - { - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return; - Debug.LogMessage(LogEventLevel.Information, "Rebooting..."); - - - var response = string.Empty; - CrestronConsole.SendControlSystemCommand("reboot", ref response); - } - - /// - /// ProgramReset method - /// - public static void ProgramReset(uint index) - { - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return; - Debug.LogMessage(LogEventLevel.Information, "Resetting Program {0}...", index); - - if (index <= 0 || index > 10) return; - - var cmd = string.Format("progreset -p:{0}", index); - - var response = string.Empty; - CrestronConsole.SendControlSystemCommand(cmd, ref response); - } - - private void CrestronEnvironmentOnEthernetEventHandler(EthernetEventArgs ethernetEventArgs) - { - if (ethernetEventArgs.EthernetEventType != eEthernetEventType.LinkUp) return; - - foreach (var fb in EthernetStatusFeedbackCollection) - { - fb.Value.UpdateEthernetStatus(); - } - } - - private void CreateEthernetStatusFeedbacks() - { - EthernetStatusFeedbackCollection = new Dictionary(); - - Debug.LogMessage(LogEventLevel.Verbose, "Creating {0} EthernetStatusFeedbacks", InitialParametersClass.NumberOfEthernetInterfaces); - - for (short i = 0; i < InitialParametersClass.NumberOfEthernetInterfaces; i++) - { - Debug.LogMessage(LogEventLevel.Verbose, "Creating EthernetStatusFeedback for Interface {0}", i); - var ethernetInterface = new EthernetStatusFeedbacks(i); - EthernetStatusFeedbackCollection.Add(i, ethernetInterface); - } - } - - private void UpdateEthernetStatusFeeedbacks() - { - foreach (var iface in EthernetStatusFeedbackCollection) - { - iface.Value.CurrentIpAddressFeedback.FireUpdate(); - iface.Value.CurrentSubnetMaskFeedback.FireUpdate(); - iface.Value.CurrentDefaultGatewayFeedback.FireUpdate(); - iface.Value.StaticIpAddressFeedback.FireUpdate(); - iface.Value.StaticSubnetMaskFeedback.FireUpdate(); - iface.Value.StaticDefaultGatewayFeedback.FireUpdate(); - iface.Value.HostNameFeedback.FireUpdate(); - iface.Value.DnsServerFeedback.FireUpdate(); - iface.Value.DomainFeedback.FireUpdate(); - iface.Value.DhcpStatusFeedback.FireUpdate(); - iface.Value.MacAddressFeedback.FireUpdate(); - } - } - - /// - /// Gets data in separate thread - /// - private void RefreshSystemMonitorData() - { - // this takes a while, launch a new thread - Task.Run(() => UpdateFeedback(null)); - } - - private void UpdateFeedback(object o) - { - TimeZoneFeedback.FireUpdate(); - TimeZoneTextFeedback.FireUpdate(); - IoControllerVersionFeedback.FireUpdate(); - SnmpVersionFeedback.FireUpdate(); - BaCnetAppVersionFeedback.FireUpdate(); - ControllerVersionFeedback.FireUpdate(); - SerialNumberFeedback.FireUpdate(); - ModelFeedback.FireUpdate(); - - IsApplianceFeedback.FireUpdate(); - IsServerFeedback.FireUpdate(); - - OnSystemMonitorPropertiesChanged(); - } - - private void OnSystemMonitorPropertiesChanged() - { - var handler = SystemMonitorPropertiesChanged; - if (handler != null) - { - handler(this, new EventArgs()); - } - } - - /// - protected override bool CustomActivate() - { - RefreshSystemMonitorData(); - - return base.CustomActivate(); - } - - /// - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new SystemMonitorJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); - } - - Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - Debug.LogMessage(LogEventLevel.Verbose, this, "Linking API starting at join: {0}", joinStart); - - TimeZoneFeedback.LinkInputSig(trilist.UShortInput[joinMap.TimeZone.JoinNumber]); - TimeZoneTextFeedback.LinkInputSig(trilist.StringInput[joinMap.TimeZoneName.JoinNumber]); - - IoControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.IOControllerVersion.JoinNumber]); - SnmpVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.SnmpAppVersion.JoinNumber]); - BaCnetAppVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.BACnetAppVersion.JoinNumber]); - ControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.ControllerVersion.JoinNumber]); - SerialNumberFeedback.LinkInputSig(trilist.StringInput[joinMap.SerialNumber.JoinNumber]); - ModelFeedback.LinkInputSig(trilist.StringInput[joinMap.Model.JoinNumber]); - UptimeFeedback.LinkInputSig(trilist.StringInput[joinMap.Uptime.JoinNumber]); - LastStartFeedback.LinkInputSig(trilist.StringInput[joinMap.LastBoot.JoinNumber]); - - trilist.SetSigHeldAction(joinMap.ProcessorReboot.JoinNumber, 10000, ProcessorReboot); - - IsApplianceFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsAppliance.JoinNumber]); - IsServerFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsServer.JoinNumber]); - - // iterate the program status feedback collection and map all the joins - LinkProgramInfoJoins(this, trilist, joinMap); - - LinkEthernetInfoJoins(this, trilist, joinMap); - } - - private static void LinkEthernetInfoJoins(SystemMonitorController systemMonitorController, BasicTriList trilist, SystemMonitorJoinMap joinMap) - { - uint ethernetSlotJoinStart = 0; - foreach (var fb in systemMonitorController.EthernetStatusFeedbackCollection) - { - fb.Value.CurrentIpAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentIpAddress.JoinNumber]); - fb.Value.CurrentSubnetMaskFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentSubnetMask.JoinNumber]); - fb.Value.CurrentDefaultGatewayFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentDefaultGateway.JoinNumber]); - fb.Value.StaticIpAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticIpAddress.JoinNumber]); - fb.Value.StaticSubnetMaskFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticSubnetMask.JoinNumber]); - fb.Value.StaticDefaultGatewayFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticDefaultGateway.JoinNumber]); - fb.Value.HostNameFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.HostName.JoinNumber]); - fb.Value.MacAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.MacAddress.JoinNumber]); - fb.Value.DomainFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.Domain.JoinNumber]); - fb.Value.DnsServerFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.DnsServer.JoinNumber]); - fb.Value.DhcpStatusFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.DhcpStatus.JoinNumber]); - - ethernetSlotJoinStart += joinMap.EthernetOffsetJoin.JoinNumber; - } - } - - private static void LinkProgramInfoJoins(SystemMonitorController systemMonitorController, BasicTriList trilist, - SystemMonitorJoinMap joinMap) - { - uint programSlotJoinStart = 0; - - foreach (var p in systemMonitorController.ProgramStatusFeedbackCollection) - { - var programNumber = p.Value.Program.Number; - - trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStart.JoinNumber, - b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Start); - p.Value.ProgramStartedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStart.JoinNumber]); - - trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStop.JoinNumber, - b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Stop); - p.Value.ProgramStoppedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStop.JoinNumber]); - - trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramRegister.JoinNumber, - b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Register); - p.Value.ProgramRegisteredFeedback.LinkInputSig( - trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramRegister.JoinNumber]); - - trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramUnregister.JoinNumber, - b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Unregister); - p.Value.ProgramUnregisteredFeedback.LinkInputSig( - trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramUnregister.JoinNumber]); - - p.Value.ProgramNameFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramName.JoinNumber]); - p.Value.ProgramCompileTimeFeedback.LinkInputSig( - trilist.StringInput[programSlotJoinStart + joinMap.ProgramCompiledTime.JoinNumber]); - p.Value.CrestronDataBaseVersionFeedback.LinkInputSig( - trilist.StringInput[programSlotJoinStart + joinMap.ProgramCrestronDatabaseVersion.JoinNumber]); - p.Value.EnvironmentVersionFeedback.LinkInputSig( - trilist.StringInput[programSlotJoinStart + joinMap.ProgramEnvironmentVersion.JoinNumber]); - p.Value.AggregatedProgramInfoFeedback.LinkInputSig( - trilist.StringInput[programSlotJoinStart + joinMap.AggregatedProgramInfo.JoinNumber]); - - trilist.SetSigHeldAction(programSlotJoinStart + joinMap.ProgramReset.JoinNumber, 10000, () => ProgramReset(programNumber)); - - programSlotJoinStart = programSlotJoinStart + joinMap.ProgramOffsetJoin.JoinSpan; - } - } - - //// Sets the time zone - //public void SetTimeZone(int timeZone) - //{ - // SystemMonitor.TimeZoneInformation.TimeZoneNumber = timeZone; - //} - - /// - /// Responds to program change events and triggers the appropriate feedbacks to update - /// - /// - /// - private void SystemMonitor_ProgramChange(Program sender, ProgramEventArgs args) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Program Change Detected for slot: {0}", sender.Number); - Debug.LogMessage(LogEventLevel.Verbose, this, "Event Type: {0}", args.EventType); - - var program = ProgramStatusFeedbackCollection[sender.Number]; - - switch (args.EventType) - { - case eProgramChangeEventType.OperatingState: - program.ProgramStartedFeedback.FireUpdate(); - program.ProgramStoppedFeedback.FireUpdate(); - program.ProgramInfo.OperatingState = args.OperatingState; - if (args.OperatingState == eProgramOperatingState.Start) - program.GetProgramInfo(); - else - { - program.AggregatedProgramInfoFeedback.FireUpdate(); - program.OnProgramInfoChanged(); - } - break; - case eProgramChangeEventType.RegistrationState: - program.ProgramRegisteredFeedback.FireUpdate(); - program.ProgramUnregisteredFeedback.FireUpdate(); - program.ProgramInfo.RegistrationState = args.RegistrationState; - program.GetProgramInfo(); - break; - } - } - - /// - /// Responds to time zone changes and updates the appropriate feedbacks - /// - /// - private void TimeZoneInformation_TimeZoneChange(TimeZoneEventArgs args) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Time Zone Change Detected."); - TimeZoneFeedback.FireUpdate(); - TimeZoneTextFeedback.FireUpdate(); - - OnSystemMonitorPropertiesChanged(); - } - - - /// - /// Represents an EthernetStatusFeedbacks - /// - public class EthernetStatusFeedbacks - { - /// - /// Gets or sets the HostNameFeedback - /// - public StringFeedback HostNameFeedback { get; protected set; } + private string _uptime; + private string _lastStart; /// - /// Gets or sets the DnsServerFeedback + /// Event fired when any SystemMonitor property changes /// - public StringFeedback DnsServerFeedback { get; protected set; } + public event EventHandler SystemMonitorPropertiesChanged; /// - /// Gets or sets the DomainFeedback + /// Gets or sets the ProgramStatusFeedbackCollection /// - public StringFeedback DomainFeedback { get; protected set; } + public Dictionary ProgramStatusFeedbackCollection; /// - /// Gets or sets the MacAddressFeedback + /// Gets or sets the EthernetStatusFeedbackCollection /// - public StringFeedback MacAddressFeedback { get; protected set; } + public Dictionary EthernetStatusFeedbackCollection; /// - /// Gets or sets the DhcpStatusFeedback + /// Gets or sets the TimeZoneFeedback /// - public StringFeedback DhcpStatusFeedback { get; protected set; } - + public IntFeedback TimeZoneFeedback { get; protected set; } /// - /// Gets or sets the CurrentIpAddressFeedback + /// Gets or sets the TimeZoneTextFeedback /// - public StringFeedback CurrentIpAddressFeedback { get; protected set; } + public StringFeedback TimeZoneTextFeedback { get; protected set; } /// - /// Gets or sets the CurrentSubnetMaskFeedback + /// Gets or sets the IoControllerVersionFeedback /// - public StringFeedback CurrentSubnetMaskFeedback { get; protected set; } + public StringFeedback IoControllerVersionFeedback { get; protected set; } /// - /// Gets or sets the CurrentDefaultGatewayFeedback + /// Gets or sets the SnmpVersionFeedback /// - public StringFeedback CurrentDefaultGatewayFeedback { get; protected set; } + public StringFeedback SnmpVersionFeedback { get; protected set; } /// - /// Gets or sets the StaticIpAddressFeedback + /// Gets or sets the BaCnetAppVersionFeedback /// - public StringFeedback StaticIpAddressFeedback { get; protected set; } + public StringFeedback BaCnetAppVersionFeedback { get; protected set; } /// - /// Gets or sets the StaticSubnetMaskFeedback + /// Gets or sets the ControllerVersionFeedback /// - public StringFeedback StaticSubnetMaskFeedback { get; protected set; } + public StringFeedback ControllerVersionFeedback { get; protected set; } + + //new feedbacks. Issue #50 + /// + /// Gets or sets the SerialNumberFeedback + /// + public StringFeedback SerialNumberFeedback { get; protected set; } /// - /// Gets or sets the StaticDefaultGatewayFeedback + /// Gets or sets the ModelFeedback /// - public StringFeedback StaticDefaultGatewayFeedback { get; protected set; } + public StringFeedback ModelFeedback { get; set; } + + /// + /// Gets or sets the UptimeFeedback + /// + public StringFeedback UptimeFeedback { get; set; } + + /// + /// Gets or sets the LastStartFeedback + /// + public StringFeedback LastStartFeedback { get; set; } + + /// + /// Gets or sets the IsApplianceFeedback + /// + public BoolFeedback IsApplianceFeedback { get; protected set; } + private bool _isApplianceFb + { + get { return CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; } + } + + /// + /// Gets or sets the IsServerFeedback + /// + public BoolFeedback IsServerFeedback { get; protected set; } + private bool _isServerFb + { + get { return CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; } + } /// /// Constructor /// - /// index of the ethernet addapter - public EthernetStatusFeedbacks(short adapterIndex) + /// device key + public SystemMonitorController(string key) + : base(key) { - Debug.LogMessage(LogEventLevel.Verbose, "Ethernet Information for interface {0}", adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Hostname: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current IP Address: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current Subnet Mask: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current Router: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static IP Address: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_IPADDRESS, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static Subnet Mask: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_IPMASK, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static Router: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_ROUTER, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} DNS Servers: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} DHCP State: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Domain Name: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterIndex), adapterIndex); - Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} MAC Address: {0}", CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterIndex), adapterIndex); - HostNameFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterIndex)); + Debug.LogMessage(LogEventLevel.Verbose, this, "Adding SystemMonitorController."); - CurrentIpAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); - CurrentDefaultGatewayFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); - CurrentSubnetMaskFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); - StaticIpAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); - StaticDefaultGatewayFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); - StaticSubnetMaskFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); - DomainFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterIndex)); - DnsServerFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterIndex)); - MacAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterIndex)); + SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; - DhcpStatusFeedback = new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterIndex)); + TimeZoneFeedback = new IntFeedback(() => SystemMonitor.TimeZoneInformation.TimeZoneNumber); + TimeZoneTextFeedback = new StringFeedback(() => SystemMonitor.TimeZoneInformation.TimeZoneName); - CurrentIpAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); - CurrentDefaultGatewayFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); - CurrentSubnetMaskFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); - StaticIpAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); - StaticDefaultGatewayFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); - StaticSubnetMaskFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); - DomainFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterIndex)); - DnsServerFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterIndex)); - MacAddressFeedback = - new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterIndex)); + IoControllerVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.IOPVersion); + SnmpVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.SNMPVersion); + BaCnetAppVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.BACNetVersion); + ControllerVersionFeedback = new StringFeedback(() => SystemMonitor.VersionInformation.ControlSystemVersion); - DhcpStatusFeedback = new StringFeedback( - () => - CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex)); + SerialNumberFeedback = new StringFeedback(() => CrestronEnvironment.SystemInfo.SerialNumber); + ModelFeedback = new StringFeedback(() => InitialParametersClass.ControllerPromptName); + UptimeFeedback = new StringFeedback(() => _uptime); + LastStartFeedback = new StringFeedback(()=> _lastStart); + + IsApplianceFeedback = new BoolFeedback(() => _isApplianceFb); + IsServerFeedback = new BoolFeedback(() => _isServerFb); + + ProgramStatusFeedbackCollection = new Dictionary(); + + foreach (var prog in SystemMonitor.ProgramCollection) + { + var program = new ProgramStatusFeedbacks(prog); + ProgramStatusFeedbackCollection.Add(prog.Number, program); + } + + CreateEthernetStatusFeedbacks(); + UpdateEthernetStatusFeeedbacks(); + + _uptimePollTimer = new CTimer(PollUptime,null,0, UptimePollTime); + + SystemMonitor.ProgramChange += SystemMonitor_ProgramChange; + SystemMonitor.TimeZoneInformation.TimeZoneChange += TimeZoneInformation_TimeZoneChange; + CrestronEnvironment.EthernetEventHandler += CrestronEnvironmentOnEthernetEventHandler; + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironmentOnProgramStatusEventHandler; + } + + private void CrestronEnvironmentOnProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType != eProgramStatusEventType.Stopping) return; + + _uptimePollTimer.Stop(); + _uptimePollTimer.Dispose(); + _uptimePollTimer = null; } /// - /// Updates all the ethernet status feedbacks for this interface + /// PollUptime method /// - public void UpdateEthernetStatus() + public void PollUptime(object obj) { - HostNameFeedback.FireUpdate(); - CurrentIpAddressFeedback.FireUpdate(); - CurrentSubnetMaskFeedback.FireUpdate(); - CurrentDefaultGatewayFeedback.FireUpdate(); - StaticIpAddressFeedback.FireUpdate(); - StaticSubnetMaskFeedback.FireUpdate(); - StaticDefaultGatewayFeedback.FireUpdate(); - DomainFeedback.FireUpdate(); - DnsServerFeedback.FireUpdate(); - MacAddressFeedback.FireUpdate(); - DhcpStatusFeedback.FireUpdate(); + var consoleResponse = string.Empty; + + CrestronConsole.SendControlSystemCommand("uptime", ref consoleResponse); + + ParseUptime(consoleResponse); + + UptimeFeedback.FireUpdate(); + LastStartFeedback.FireUpdate(); } - } -} - -/// -/// Represents a collection of feedbacks related to the status of a program running on the control system, including its operating state, registration state, and various pieces of information about the program such as its name, compile time, and environment. This class also includes methods for retrieving program information and responding to program change events to keep the feedbacks updated in real-time. -/// -public class ProgramStatusFeedbacks -{ - /// - /// Event that is fired when any of the program information properties change, allowing external classes to respond to changes in program information in real-time - /// - public event EventHandler ProgramInfoChanged; - - /// - /// Gets or sets the Program associated with this collection of feedbacks - /// - public Program Program; - - /// - /// Gets or sets the ProgramInfo object that contains detailed information about the program, such as its file name, compile time, environment, and other relevant properties. This object is updated whenever there is a change in the program's operating state or registration state, ensuring that the feedbacks always reflect the most current information about the program. - /// - public ProgramInfo ProgramInfo { get; set; } - - /// - /// Gets or sets the ProgramStartedFeedback, which is a BoolFeedback that indicates whether the program is currently started (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is running or not. - /// - public BoolFeedback ProgramStartedFeedback; - - /// - /// Gets or sets the ProgramStoppedFeedback, which is a BoolFeedback that indicates whether the program is currently stopped (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is stopped or not. - /// - public BoolFeedback ProgramStoppedFeedback; - - /// - /// Gets or sets the ProgramRegisteredFeedback, which is a BoolFeedback that indicates whether the program is currently registered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is registered or not. - /// - public BoolFeedback ProgramRegisteredFeedback; - - /// - /// Gets or sets the ProgramUnregisteredFeedback, which is a BoolFeedback that indicates whether the program is currently unregistered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is unregistered or not. - /// - public BoolFeedback ProgramUnregisteredFeedback; - - /// - /// Gets or sets the ProgramNameFeedback, which is a StringFeedback that provides the name of the program file. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current name of the program file. - /// - public StringFeedback ProgramNameFeedback; - - /// - /// Gets or sets the ProgramCompileTimeFeedback, which is a StringFeedback that provides the compile time of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current compile time of the program. - /// - public StringFeedback ProgramCompileTimeFeedback; - - /// - /// Gets or sets the CrestronDataBaseVersionFeedback, which is a StringFeedback that provides the version of the Crestron database used by the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current version of the Crestron database used by the program. - /// - public StringFeedback CrestronDataBaseVersionFeedback; - - /// - /// Gets or sets the EnvironmentVersionFeedback, which is a StringFeedback that provides the environment version of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current environment version of the program. - /// - public StringFeedback EnvironmentVersionFeedback; - - /// - /// Gets or sets the AggregatedProgramInfoFeedback, which is a StringFeedback that provides a JSON serialized string of the entire ProgramInfo object. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access all current information about the program in a single feedback. - /// - public StringFeedback AggregatedProgramInfoFeedback; - - - /// - /// Constructor for the ProgramStatusFeedbacks class, which initializes all feedbacks based on the provided Program object and retrieves the initial program information to populate the feedbacks with accurate data. This constructor also sets up the necessary event handlers to ensure that the feedbacks are updated in real-time as changes occur to the program's operating state or registration state. - /// - /// - public ProgramStatusFeedbacks(Program program) - { - ProgramInfo = new ProgramInfo(program.Number); - - Program = program; - - ProgramInfo.OperatingState = Program.OperatingState; - ProgramInfo.RegistrationState = Program.RegistrationState; - - ProgramStartedFeedback = new BoolFeedback(() => Program.OperatingState == eProgramOperatingState.Start); - ProgramStartedFeedback.FireUpdate(); - - ProgramStoppedFeedback = new BoolFeedback(() => Program.OperatingState == eProgramOperatingState.Stop); - ProgramStoppedFeedback.FireUpdate(); - - ProgramRegisteredFeedback = - new BoolFeedback(() => Program.RegistrationState == eProgramRegistrationState.Register); - ProgramRegisteredFeedback.FireUpdate(); - - ProgramUnregisteredFeedback = - new BoolFeedback(() => Program.RegistrationState == eProgramRegistrationState.Unregister); - ProgramUnregisteredFeedback.FireUpdate(); - - ProgramNameFeedback = new StringFeedback(() => ProgramInfo.ProgramFile); - CrestronDataBaseVersionFeedback = new StringFeedback(() => ProgramInfo.CrestronDb); - EnvironmentVersionFeedback = new StringFeedback(() => ProgramInfo.Environment); - ProgramCompileTimeFeedback = new StringFeedback(() => ProgramInfo.CompileTime); - AggregatedProgramInfoFeedback = new StringFeedback(() => JsonConvert.SerializeObject(ProgramInfo)); - - GetProgramInfo(); - } - - /// - /// Retrieves information about a running program - /// - public void GetProgramInfo() - { - Task.Run(() => GetProgramInfo(null)); - } - - private void GetProgramInfo(object o) - { - Debug.LogMessage(LogEventLevel.Verbose, "Attempting to get program info for slot: {0}", Program.Number); - - string response = null; - - if (Program.RegistrationState == eProgramRegistrationState.Unregister || Program.OperatingState == eProgramOperatingState.Stop) + private void ParseUptime(string response) { - Debug.LogMessage(LogEventLevel.Verbose, "Program {0} not registered. Setting default values for program information.", - Program.Number); + var splitString = response.Trim().Split('\r', '\n'); - ProgramInfo = new ProgramInfo(Program.Number) + var lastStartRaw = splitString.FirstOrDefault(o => o.Contains("started")); + var uptimeRaw = splitString.FirstOrDefault(o => o.Contains("running")); + + if (!String.IsNullOrEmpty(lastStartRaw)) { - OperatingState = Program.OperatingState, - RegistrationState = Program.RegistrationState - }; + var lastStartIndex = lastStartRaw.IndexOf(':'); + _lastStart = lastStartRaw.Substring(lastStartIndex + 1).Trim(); + } - return; + if (String.IsNullOrEmpty(uptimeRaw)) return; + var forIndex = uptimeRaw.IndexOf("for", StringComparison.Ordinal); + + //4 => "for " to get what's on the right + _uptime = uptimeRaw.Substring(forIndex + 4); } - var success = CrestronConsole.SendControlSystemCommand( - string.Format("progcomments:{0}", Program.Number), ref response); + /// + /// ProcessorReboot method + /// + public static void ProcessorReboot() + { + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return; + Debug.LogMessage(LogEventLevel.Information, "Rebooting..."); - if (!success) + + var response = string.Empty; + CrestronConsole.SendControlSystemCommand("reboot", ref response); + } + + /// + /// ProgramReset method + /// + public static void ProgramReset(uint index) + { + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return; + Debug.LogMessage(LogEventLevel.Information, "Resetting Program {0}...", index); + + if (index <= 0 || index > 10) return; + + var cmd = string.Format("progreset -p:{0}", index); + + var response = string.Empty; + CrestronConsole.SendControlSystemCommand(cmd, ref response); + } + + private void CrestronEnvironmentOnEthernetEventHandler(EthernetEventArgs ethernetEventArgs) { - Debug.LogMessage(LogEventLevel.Verbose, "Progcomments Attempt Unsuccessful for slot: {0}", Program.Number); - UpdateFeedbacks(); - return; - } + if (ethernetEventArgs.EthernetEventType != eEthernetEventType.LinkUp) return; - if (response.ToLower().Contains("bad or incomplete")) - { - Debug.LogMessage(LogEventLevel.Verbose, - "Program in slot {0} not running. Setting default ProgramInfo for slot: {0}", - Program.Number); - - // Assume no valid program info. Constructing a new object will wipe all properties - ProgramInfo = new ProgramInfo(Program.Number) + foreach (var fb in EthernetStatusFeedbackCollection) { - OperatingState = Program.OperatingState, - RegistrationState = Program.RegistrationState - }; - - UpdateFeedbacks(); - - return; + fb.Value.UpdateEthernetStatus(); + } } - - // Shared properteis - ProgramInfo.ProgramFile = ParseConsoleData(response, "Program File", ": ", "\n"); - ProgramInfo.CompilerRevision = ParseConsoleData(response, "Compiler Rev", ": ", "\n"); - ProgramInfo.CompileTime = ParseConsoleData(response, "Compiled On", ": ", "\n"); - ProgramInfo.Include4Dat = ParseConsoleData(response, "Include4.dat", ": ", "\n"); - - - if (ProgramInfo.ProgramFile.Contains(".dll")) + private void CreateEthernetStatusFeedbacks() { - // SSP Program - ProgramInfo.FriendlyName = ParseConsoleData(response, "Friendly Name", ": ", "\n"); - ProgramInfo.ApplicationName = ParseConsoleData(response, "Application Name", ": ", "\n"); - ProgramInfo.ProgramTool = ParseConsoleData(response, "Program Tool", ": ", "\n"); - ProgramInfo.MinFirmwareVersion = ParseConsoleData(response, "Min Firmware Version", ": ", - "\n"); - ProgramInfo.PlugInVersion = ParseConsoleData(response, "PlugInVersion", ": ", "\n"); + EthernetStatusFeedbackCollection = new Dictionary(); - ProgramInfo.ProgramFile += string.Format(" {0}.{1}.{2}", - ProgramInfo.CompilerRevisionInfo.Major, - ProgramInfo.CompilerRevisionInfo.Minor, - ProgramInfo.CompilerRevisionInfo.Build); + Debug.LogMessage(LogEventLevel.Verbose, "Creating {0} EthernetStatusFeedbacks", InitialParametersClass.NumberOfEthernetInterfaces); - ProgramInfo.Environment = ProgramInfo.ProgramTool; + for (short i = 0; i < InitialParametersClass.NumberOfEthernetInterfaces; i++) + { + Debug.LogMessage(LogEventLevel.Verbose, "Creating EthernetStatusFeedback for Interface {0}", i); + var ethernetInterface = new EthernetStatusFeedbacks(i); + EthernetStatusFeedbackCollection.Add(i, ethernetInterface); + } } - else if (ProgramInfo.ProgramFile.Contains(".smw")) + + private void UpdateEthernetStatusFeeedbacks() { - // SIMPL Windows Program - ProgramInfo.FriendlyName = ParseConsoleData(response, "Friendly Name", ":", "\n"); - ProgramInfo.SystemName = ParseConsoleData(response, "System Name", ": ", "\n"); - ProgramInfo.CrestronDb = ParseConsoleData(response, "CrestronDB", ": ", "\n"); - ProgramInfo.Environment = ParseConsoleData(response, "Source Env", ": ", "\n"); - ProgramInfo.Programmer = ParseConsoleData(response, "Programmer", ": ", "\n"); + foreach (var iface in EthernetStatusFeedbackCollection) + { + iface.Value.CurrentIpAddressFeedback.FireUpdate(); + iface.Value.CurrentSubnetMaskFeedback.FireUpdate(); + iface.Value.CurrentDefaultGatewayFeedback.FireUpdate(); + iface.Value.StaticIpAddressFeedback.FireUpdate(); + iface.Value.StaticSubnetMaskFeedback.FireUpdate(); + iface.Value.StaticDefaultGatewayFeedback.FireUpdate(); + iface.Value.HostNameFeedback.FireUpdate(); + iface.Value.DnsServerFeedback.FireUpdate(); + iface.Value.DomainFeedback.FireUpdate(); + iface.Value.DhcpStatusFeedback.FireUpdate(); + iface.Value.MacAddressFeedback.FireUpdate(); + } } - Debug.LogMessage(LogEventLevel.Verbose, "Program info for slot {0} successfully updated", Program.Number); - UpdateFeedbacks(); + /// + /// Gets data in separate thread + /// + private void RefreshSystemMonitorData() + { + // this takes a while, launch a new thread + CrestronInvoke.BeginInvoke(UpdateFeedback); + } + + private void UpdateFeedback(object o) + { + TimeZoneFeedback.FireUpdate(); + TimeZoneTextFeedback.FireUpdate(); + IoControllerVersionFeedback.FireUpdate(); + SnmpVersionFeedback.FireUpdate(); + BaCnetAppVersionFeedback.FireUpdate(); + ControllerVersionFeedback.FireUpdate(); + SerialNumberFeedback.FireUpdate(); + ModelFeedback.FireUpdate(); + + IsApplianceFeedback.FireUpdate(); + IsServerFeedback.FireUpdate(); + + OnSystemMonitorPropertiesChanged(); + } + + private void OnSystemMonitorPropertiesChanged() + { + var handler = SystemMonitorPropertiesChanged; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + RefreshSystemMonitorData(); + + return base.CustomActivate(); + } + + /// + /// LinkToApi method + /// + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new SystemMonitorJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + Debug.LogMessage(LogEventLevel.Verbose, this, "Linking API starting at join: {0}", joinStart); + + TimeZoneFeedback.LinkInputSig(trilist.UShortInput[joinMap.TimeZone.JoinNumber]); + TimeZoneTextFeedback.LinkInputSig(trilist.StringInput[joinMap.TimeZoneName.JoinNumber]); + + IoControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.IOControllerVersion.JoinNumber]); + SnmpVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.SnmpAppVersion.JoinNumber]); + BaCnetAppVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.BACnetAppVersion.JoinNumber]); + ControllerVersionFeedback.LinkInputSig(trilist.StringInput[joinMap.ControllerVersion.JoinNumber]); + SerialNumberFeedback.LinkInputSig(trilist.StringInput[joinMap.SerialNumber.JoinNumber]); + ModelFeedback.LinkInputSig(trilist.StringInput[joinMap.Model.JoinNumber]); + UptimeFeedback.LinkInputSig(trilist.StringInput[joinMap.Uptime.JoinNumber]); + LastStartFeedback.LinkInputSig(trilist.StringInput[joinMap.LastBoot.JoinNumber]); + + trilist.SetSigHeldAction(joinMap.ProcessorReboot.JoinNumber, 10000, ProcessorReboot); + + IsApplianceFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsAppliance.JoinNumber]); + IsServerFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsServer.JoinNumber]); + + // iterate the program status feedback collection and map all the joins + LinkProgramInfoJoins(this, trilist, joinMap); + + LinkEthernetInfoJoins(this, trilist, joinMap); + } + + private static void LinkEthernetInfoJoins(SystemMonitorController systemMonitorController, BasicTriList trilist, SystemMonitorJoinMap joinMap) + { + uint ethernetSlotJoinStart = 0; + foreach (var fb in systemMonitorController.EthernetStatusFeedbackCollection) + { + fb.Value.CurrentIpAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentIpAddress.JoinNumber]); + fb.Value.CurrentSubnetMaskFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentSubnetMask.JoinNumber]); + fb.Value.CurrentDefaultGatewayFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.CurrentDefaultGateway.JoinNumber]); + fb.Value.StaticIpAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticIpAddress.JoinNumber]); + fb.Value.StaticSubnetMaskFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticSubnetMask.JoinNumber]); + fb.Value.StaticDefaultGatewayFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.StaticDefaultGateway.JoinNumber]); + fb.Value.HostNameFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.HostName.JoinNumber]); + fb.Value.MacAddressFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.MacAddress.JoinNumber]); + fb.Value.DomainFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.Domain.JoinNumber]); + fb.Value.DnsServerFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.DnsServer.JoinNumber]); + fb.Value.DhcpStatusFeedback.LinkInputSig(trilist.StringInput[ethernetSlotJoinStart + joinMap.DhcpStatus.JoinNumber]); + + ethernetSlotJoinStart += joinMap.EthernetOffsetJoin.JoinNumber; + } + } + + private static void LinkProgramInfoJoins(SystemMonitorController systemMonitorController, BasicTriList trilist, + SystemMonitorJoinMap joinMap) + { + uint programSlotJoinStart = 0; + + foreach (var p in systemMonitorController.ProgramStatusFeedbackCollection) + { + var programNumber = p.Value.Program.Number; + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStart.JoinNumber, + b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Start); + p.Value.ProgramStartedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStart.JoinNumber]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramStop.JoinNumber, + b => SystemMonitor.ProgramCollection[programNumber].OperatingState = eProgramOperatingState.Stop); + p.Value.ProgramStoppedFeedback.LinkInputSig(trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramStop.JoinNumber]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramRegister.JoinNumber, + b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Register); + p.Value.ProgramRegisteredFeedback.LinkInputSig( + trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramRegister.JoinNumber]); + + trilist.SetBoolSigAction(programSlotJoinStart + joinMap.ProgramUnregister.JoinNumber, + b => SystemMonitor.ProgramCollection[programNumber].RegistrationState = eProgramRegistrationState.Unregister); + p.Value.ProgramUnregisteredFeedback.LinkInputSig( + trilist.BooleanInput[programSlotJoinStart + joinMap.ProgramUnregister.JoinNumber]); + + p.Value.ProgramNameFeedback.LinkInputSig(trilist.StringInput[programSlotJoinStart + joinMap.ProgramName.JoinNumber]); + p.Value.ProgramCompileTimeFeedback.LinkInputSig( + trilist.StringInput[programSlotJoinStart + joinMap.ProgramCompiledTime.JoinNumber]); + p.Value.CrestronDataBaseVersionFeedback.LinkInputSig( + trilist.StringInput[programSlotJoinStart + joinMap.ProgramCrestronDatabaseVersion.JoinNumber]); + p.Value.EnvironmentVersionFeedback.LinkInputSig( + trilist.StringInput[programSlotJoinStart + joinMap.ProgramEnvironmentVersion.JoinNumber]); + p.Value.AggregatedProgramInfoFeedback.LinkInputSig( + trilist.StringInput[programSlotJoinStart + joinMap.AggregatedProgramInfo.JoinNumber]); + + trilist.SetSigHeldAction(programSlotJoinStart + joinMap.ProgramReset.JoinNumber, 10000, () => ProgramReset(programNumber)); + + programSlotJoinStart = programSlotJoinStart + joinMap.ProgramOffsetJoin.JoinSpan; + } + } + + //// Sets the time zone + //public void SetTimeZone(int timeZone) + //{ + // SystemMonitor.TimeZoneInformation.TimeZoneNumber = timeZone; + //} + + /// + /// Responds to program change events and triggers the appropriate feedbacks to update + /// + /// + /// + private void SystemMonitor_ProgramChange(Program sender, ProgramEventArgs args) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Program Change Detected for slot: {0}", sender.Number); + Debug.LogMessage(LogEventLevel.Verbose, this, "Event Type: {0}", args.EventType); + + var program = ProgramStatusFeedbackCollection[sender.Number]; + + switch (args.EventType) + { + case eProgramChangeEventType.OperatingState: + program.ProgramStartedFeedback.FireUpdate(); + program.ProgramStoppedFeedback.FireUpdate(); + program.ProgramInfo.OperatingState = args.OperatingState; + if (args.OperatingState == eProgramOperatingState.Start) + program.GetProgramInfo(); + else + { + program.AggregatedProgramInfoFeedback.FireUpdate(); + program.OnProgramInfoChanged(); + } + break; + case eProgramChangeEventType.RegistrationState: + program.ProgramRegisteredFeedback.FireUpdate(); + program.ProgramUnregisteredFeedback.FireUpdate(); + program.ProgramInfo.RegistrationState = args.RegistrationState; + program.GetProgramInfo(); + break; + } + } + + /// + /// Responds to time zone changes and updates the appropriate feedbacks + /// + /// + private void TimeZoneInformation_TimeZoneChange(TimeZoneEventArgs args) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Time Zone Change Detected."); + TimeZoneFeedback.FireUpdate(); + TimeZoneTextFeedback.FireUpdate(); + + OnSystemMonitorPropertiesChanged(); + } + + /// + /// Represents an EthernetStatusFeedbacks + /// + public class EthernetStatusFeedbacks + { + /// + /// Gets or sets the HostNameFeedback + /// + public StringFeedback HostNameFeedback { get; protected set; } + + /// + /// Gets or sets the DnsServerFeedback + /// + public StringFeedback DnsServerFeedback { get; protected set; } + + /// + /// Gets or sets the DomainFeedback + /// + public StringFeedback DomainFeedback { get; protected set; } + + /// + /// Gets or sets the MacAddressFeedback + /// + public StringFeedback MacAddressFeedback { get; protected set; } + + /// + /// Gets or sets the DhcpStatusFeedback + /// + public StringFeedback DhcpStatusFeedback { get; protected set; } + + + /// + /// Gets or sets the CurrentIpAddressFeedback + /// + public StringFeedback CurrentIpAddressFeedback { get; protected set; } + + /// + /// Gets or sets the CurrentSubnetMaskFeedback + /// + public StringFeedback CurrentSubnetMaskFeedback { get; protected set; } + + /// + /// Gets or sets the CurrentDefaultGatewayFeedback + /// + public StringFeedback CurrentDefaultGatewayFeedback { get; protected set; } + + /// + /// Gets or sets the StaticIpAddressFeedback + /// + public StringFeedback StaticIpAddressFeedback { get; protected set; } + + /// + /// Gets or sets the StaticSubnetMaskFeedback + /// + public StringFeedback StaticSubnetMaskFeedback { get; protected set; } + + /// + /// Gets or sets the StaticDefaultGatewayFeedback + /// + public StringFeedback StaticDefaultGatewayFeedback { get; protected set; } + + /// + /// Constructor + /// + /// index of the ethernet addapter + public EthernetStatusFeedbacks(short adapterIndex) + { + Debug.LogMessage(LogEventLevel.Verbose, "Ethernet Information for interface {0}", adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Hostname: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current IP Address: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current Subnet Mask: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Current Router: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static IP Address: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_IPADDRESS, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static Subnet Mask: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_IPMASK, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Static Router: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_STATIC_ROUTER, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} DNS Servers: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} DHCP State: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} Domain Name: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterIndex), adapterIndex); + Debug.LogMessage(LogEventLevel.Verbose, "Adapter Index: {1} MAC Address: {0}", CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterIndex), adapterIndex); + HostNameFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterIndex)); + + CurrentIpAddressFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); + CurrentDefaultGatewayFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); + CurrentSubnetMaskFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); + StaticIpAddressFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterIndex)); + StaticDefaultGatewayFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterIndex)); + StaticSubnetMaskFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterIndex)); + DomainFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterIndex)); + DnsServerFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterIndex)); + MacAddressFeedback = + new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterIndex)); + + DhcpStatusFeedback = new StringFeedback( + () => + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex)); + } + + /// + /// UpdateEthernetStatus method + /// + public void UpdateEthernetStatus() + { + HostNameFeedback.FireUpdate(); + CurrentIpAddressFeedback.FireUpdate(); + CurrentSubnetMaskFeedback.FireUpdate(); + CurrentDefaultGatewayFeedback.FireUpdate(); + StaticIpAddressFeedback.FireUpdate(); + StaticSubnetMaskFeedback.FireUpdate(); + StaticDefaultGatewayFeedback.FireUpdate(); + DomainFeedback.FireUpdate(); + DnsServerFeedback.FireUpdate(); + MacAddressFeedback.FireUpdate(); + DhcpStatusFeedback.FireUpdate(); + } + } + + + /// + /// Represents a ProgramStatusFeedbacks + /// + public class ProgramStatusFeedbacks + { + /// + /// Event fired when ProgramInfo changes + /// + public event EventHandler ProgramInfoChanged; + + /// + /// Gets or sets the Program + /// + public Program Program; + + /// + /// Gets or sets the ProgramInfo + /// + public ProgramInfo ProgramInfo { get; set; } + + /// + /// Gets or sets the ProgramStartedFeedback + /// + public BoolFeedback ProgramStartedFeedback; + + /// + /// Gets or sets the ProgramStoppedFeedback + /// + public BoolFeedback ProgramStoppedFeedback; + + /// + /// Gets or sets the ProgramRegisteredFeedback + /// + public BoolFeedback ProgramRegisteredFeedback; + + /// + /// Gets or sets the ProgramUnregisteredFeedback + /// + public BoolFeedback ProgramUnregisteredFeedback; + + /// + /// Gets or sets the ProgramNameFeedback + /// + public StringFeedback ProgramNameFeedback; + + /// + /// Gets or sets the ProgramCompileTimeFeedback + /// + public StringFeedback ProgramCompileTimeFeedback; + + /// + /// Gets or sets the CrestronDataBaseVersionFeedback + /// + public StringFeedback CrestronDataBaseVersionFeedback; + + // SIMPL windows version + /// + /// Gets or sets the EnvironmentVersionFeedback + /// + public StringFeedback EnvironmentVersionFeedback; + + /// + /// Gets or sets the AggregatedProgramInfoFeedback + /// + public StringFeedback AggregatedProgramInfoFeedback; + + /// + /// Constructor + /// + /// program to get status about + public ProgramStatusFeedbacks(Program program) + { + ProgramInfo = new ProgramInfo(program.Number); + + Program = program; + + ProgramInfo.OperatingState = Program.OperatingState; + ProgramInfo.RegistrationState = Program.RegistrationState; + + ProgramStartedFeedback = new BoolFeedback(() => Program.OperatingState == eProgramOperatingState.Start); + ProgramStartedFeedback.FireUpdate(); + + ProgramStoppedFeedback = new BoolFeedback(() => Program.OperatingState == eProgramOperatingState.Stop); + ProgramStoppedFeedback.FireUpdate(); + + ProgramRegisteredFeedback = + new BoolFeedback(() => Program.RegistrationState == eProgramRegistrationState.Register); + ProgramRegisteredFeedback.FireUpdate(); + + ProgramUnregisteredFeedback = + new BoolFeedback(() => Program.RegistrationState == eProgramRegistrationState.Unregister); + ProgramUnregisteredFeedback.FireUpdate(); + + ProgramNameFeedback = new StringFeedback(() => ProgramInfo.ProgramFile); + CrestronDataBaseVersionFeedback = new StringFeedback(() => ProgramInfo.CrestronDb); + EnvironmentVersionFeedback = new StringFeedback(() => ProgramInfo.Environment); + ProgramCompileTimeFeedback = new StringFeedback(() => ProgramInfo.CompileTime); + AggregatedProgramInfoFeedback = new StringFeedback(() => JsonConvert.SerializeObject(ProgramInfo)); + + GetProgramInfo(); + } + + /// + /// GetProgramInfo method + /// + public void GetProgramInfo() + { + CrestronInvoke.BeginInvoke(GetProgramInfo); + } + + private void GetProgramInfo(object o) + { + Debug.LogMessage(LogEventLevel.Verbose, "Attempting to get program info for slot: {0}", Program.Number); + + string response = null; + + if (Program.RegistrationState == eProgramRegistrationState.Unregister || Program.OperatingState == eProgramOperatingState.Stop) + { + Debug.LogMessage(LogEventLevel.Verbose, "Program {0} not registered. Setting default values for program information.", + Program.Number); + + ProgramInfo = new ProgramInfo(Program.Number) + { + OperatingState = Program.OperatingState, + RegistrationState = Program.RegistrationState + }; + + return; + } + + var success = CrestronConsole.SendControlSystemCommand( + string.Format("progcomments:{0}", Program.Number), ref response); + + if (!success) + { + Debug.LogMessage(LogEventLevel.Verbose, "Progcomments Attempt Unsuccessful for slot: {0}", Program.Number); + UpdateFeedbacks(); + return; + } + + if (response.ToLower().Contains("bad or incomplete")) + { + Debug.LogMessage(LogEventLevel.Verbose, + "Program in slot {0} not running. Setting default ProgramInfo for slot: {0}", + Program.Number); + + // Assume no valid program info. Constructing a new object will wipe all properties + ProgramInfo = new ProgramInfo(Program.Number) + { + OperatingState = Program.OperatingState, + RegistrationState = Program.RegistrationState + }; + + UpdateFeedbacks(); + + return; + } + + + // Shared properteis + ProgramInfo.ProgramFile = ParseConsoleData(response, "Program File", ": ", "\n"); + ProgramInfo.CompilerRevision = ParseConsoleData(response, "Compiler Rev", ": ", "\n"); + ProgramInfo.CompileTime = ParseConsoleData(response, "Compiled On", ": ", "\n"); + ProgramInfo.Include4Dat = ParseConsoleData(response, "Include4.dat", ": ", "\n"); + + + if (ProgramInfo.ProgramFile.Contains(".dll")) + { + // SSP Program + ProgramInfo.FriendlyName = ParseConsoleData(response, "Friendly Name", ": ", "\n"); + ProgramInfo.ApplicationName = ParseConsoleData(response, "Application Name", ": ", "\n"); + ProgramInfo.ProgramTool = ParseConsoleData(response, "Program Tool", ": ", "\n"); + ProgramInfo.MinFirmwareVersion = ParseConsoleData(response, "Min Firmware Version", ": ", + "\n"); + ProgramInfo.PlugInVersion = ParseConsoleData(response, "PlugInVersion", ": ", "\n"); + + ProgramInfo.ProgramFile += string.Format(" {0}.{1}.{2}", + ProgramInfo.CompilerRevisionInfo.Major, + ProgramInfo.CompilerRevisionInfo.Minor, + ProgramInfo.CompilerRevisionInfo.Build); + + ProgramInfo.Environment = ProgramInfo.ProgramTool; + } + else if (ProgramInfo.ProgramFile.Contains(".smw")) + { + // SIMPL Windows Program + ProgramInfo.FriendlyName = ParseConsoleData(response, "Friendly Name", ":", "\n"); + ProgramInfo.SystemName = ParseConsoleData(response, "System Name", ": ", "\n"); + ProgramInfo.CrestronDb = ParseConsoleData(response, "CrestronDB", ": ", "\n"); + ProgramInfo.Environment = ParseConsoleData(response, "Source Env", ": ", "\n"); + ProgramInfo.Programmer = ParseConsoleData(response, "Programmer", ": ", "\n"); + } + Debug.LogMessage(LogEventLevel.Verbose, "Program info for slot {0} successfully updated", Program.Number); + + UpdateFeedbacks(); + } + + private void UpdateFeedbacks() + { + ProgramNameFeedback.FireUpdate(); + ProgramCompileTimeFeedback.FireUpdate(); + CrestronDataBaseVersionFeedback.FireUpdate(); + EnvironmentVersionFeedback.FireUpdate(); + + AggregatedProgramInfoFeedback.FireUpdate(); + + OnProgramInfoChanged(); + } + + /// + /// OnProgramInfoChanged method + /// + public void OnProgramInfoChanged() + { + //Debug.LogMessage(LogEventLevel.Debug, "Firing ProgramInfoChanged for slot: {0}", Program.Number); + var handler = ProgramInfoChanged; + if (handler != null) + { + handler(this, new ProgramInfoEventArgs(ProgramInfo)); + } + } + + private string ParseConsoleData(string data, string line, string startString, string endString) + { + var outputData = ""; + + if (data.Length <= 0) return outputData; + + if (!data.Contains(line)) + { + return outputData; + } + + try + { + //Debug.LogMessage(LogEventLevel.Verbose, "ParseConsoleData Data: {0}, Line {1}, startStirng {2}, endString {3}", data, line, startString, endString); + var linePosition = data.IndexOf(line, StringComparison.Ordinal); + var startPosition = data.IndexOf(startString, linePosition, StringComparison.Ordinal) + + startString.Length; + var endPosition = data.IndexOf(endString, startPosition, StringComparison.Ordinal); + outputData = data.Substring(startPosition, endPosition - startPosition).Trim(); + //Debug.LogMessage(LogEventLevel.Verbose, "ParseConsoleData Return: {0}", outputData); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Error, "Error Parsing Console Data: {0}", e); + Debug.LogMessage(LogEventLevel.Verbose, "Stack Trace: {stackTrace}", e.StackTrace); + } + + return outputData; + } + } } - private void UpdateFeedbacks() + /// + /// Represents a ProgramInfo + /// + public class ProgramInfo { - ProgramNameFeedback.FireUpdate(); - ProgramCompileTimeFeedback.FireUpdate(); - CrestronDataBaseVersionFeedback.FireUpdate(); - EnvironmentVersionFeedback.FireUpdate(); + // Shared properties - AggregatedProgramInfoFeedback.FireUpdate(); + /// + /// Gets the ProgramNumber + /// + [JsonProperty("programNumber")] + public uint ProgramNumber { get; private set; } - OnProgramInfoChanged(); - } + /// + /// Gets or sets the OperatingState + /// + [JsonConverter(typeof (StringEnumConverter))] + [JsonProperty("operatingState")] + public eProgramOperatingState OperatingState { get; set; } - /// - /// Fires the ProgramInfoChanged event to notify external classes that they should update any properties related to program information based on changes to the program's operating state or registration state. This method is called whenever there is a change in the program's information, ensuring that all feedbacks and external classes that rely on program information are always up-to-date with the most current information about the program. - /// - public void OnProgramInfoChanged() - { - //Debug.LogMessage(LogEventLevel.Debug, "Firing ProgramInfoChanged for slot: {0}", Program.Number); - var handler = ProgramInfoChanged; - if (handler != null) + /// + /// Gets or sets the RegistrationState + /// + [JsonConverter(typeof (StringEnumConverter))] + [JsonProperty("registrationState")] + public eProgramRegistrationState RegistrationState { get; set; } + + /// + /// Gets or sets the ProgramFile + /// + [JsonProperty("programFile")] + public string ProgramFile { get; set; } + + /// + /// Gets or sets the FriendlyName + /// + [JsonProperty("friendlyName")] + public string FriendlyName { get; set; } + + /// + /// Gets or sets the CompilerRevision + /// + [JsonProperty("compilerRevision")] + public string CompilerRevision { get; set; } + + /// + /// Gets the CompilerRevisionInfo + /// + [JsonIgnore] + public Version CompilerRevisionInfo + { + get + { + return new Version(CompilerRevision); + } + } + + /// + /// Gets or sets the CompileTime + /// + [JsonProperty("compileTime")] + public string CompileTime { get; set; } + + /// + /// Gets or sets the Include4Dat + /// + [JsonProperty("include4Dat")] + public string Include4Dat { get; set; } + + // SIMPL Windows properties + + /// + /// Gets or sets the SystemName + /// + [JsonProperty("systemName")] + public string SystemName { get; set; } + + /// + /// Gets or sets the CrestronDb + /// + [JsonProperty("crestronDb")] + public string CrestronDb { get; set; } + + /// + /// Gets or sets the Environment + /// + [JsonProperty("environment")] + public string Environment { get; set; } + + /// + /// Gets or sets the Programmer + /// + [JsonProperty("programmer")] + public string Programmer { get; set; } + + + // SSP Properties + + /// + /// Gets or sets the ApplicationName + /// + [JsonProperty("applicationName")] + public string ApplicationName { get; set; } + + /// + /// Gets or sets the ProgramTool + /// + [JsonProperty("programTool")] + public string ProgramTool { get; set; } + + /// + /// Gets or sets the MinFirmwareVersion + /// + [JsonProperty("minFirmwareVersion")] + public string MinFirmwareVersion { get; set; } + + /// + /// Gets or sets the PlugInVersion + /// + [JsonProperty("plugInVersion")] + public string PlugInVersion { get; set; } + + /// + /// Constructor + /// + /// program slot to get info about + public ProgramInfo(uint number) { - handler(this, new ProgramInfoEventArgs(ProgramInfo)); - } - } + ProgramNumber = number; - private string ParseConsoleData(string data, string line, string startString, string endString) - { - var outputData = ""; + ProgramFile = ""; + FriendlyName = ""; + CompilerRevision = ""; + CompileTime = ""; + Include4Dat = ""; - if (data.Length <= 0) return outputData; + SystemName = ""; + CrestronDb = ""; + Environment = ""; + Programmer = ""; - if (!data.Contains(line)) - { - return outputData; - } - - try - { - //Debug.LogMessage(LogEventLevel.Verbose, "ParseConsoleData Data: {0}, Line {1}, startStirng {2}, endString {3}", data, line, startString, endString); - var linePosition = data.IndexOf(line, StringComparison.Ordinal); - var startPosition = data.IndexOf(startString, linePosition, StringComparison.Ordinal) + - startString.Length; - var endPosition = data.IndexOf(endString, startPosition, StringComparison.Ordinal); - outputData = data.Substring(startPosition, endPosition - startPosition).Trim(); - //Debug.LogMessage(LogEventLevel.Verbose, "ParseConsoleData Return: {0}", outputData); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Error, "Error Parsing Console Data: {0}", e); - Debug.LogMessage(LogEventLevel.Verbose, "Stack Trace: {stackTrace}", e.StackTrace); - } - - return outputData; - } -} - - -/// -/// Represents a ProgramInfo -/// -public class ProgramInfo -{ - // Shared properties - - /// - /// Gets the ProgramNumber - /// - [JsonProperty("programNumber")] - public uint ProgramNumber { get; private set; } - - /// - /// Gets or sets the OperatingState - /// - [JsonConverter(typeof(StringEnumConverter))] - [JsonProperty("operatingState")] - public eProgramOperatingState OperatingState { get; set; } - - /// - /// Gets or sets the RegistrationState - /// - [JsonConverter(typeof(StringEnumConverter))] - [JsonProperty("registrationState")] - public eProgramRegistrationState RegistrationState { get; set; } - - /// - /// Gets or sets the ProgramFile - /// - [JsonProperty("programFile")] - public string ProgramFile { get; set; } - - /// - /// Gets or sets the FriendlyName - /// - [JsonProperty("friendlyName")] - public string FriendlyName { get; set; } - - /// - /// Gets or sets the CompilerRevision - /// - [JsonProperty("compilerRevision")] - public string CompilerRevision { get; set; } - - /// - /// Gets the CompilerRevisionInfo - /// - [JsonIgnore] - public Version CompilerRevisionInfo - { - get - { - return new Version(CompilerRevision); + ApplicationName = ""; + ProgramTool = ""; + MinFirmwareVersion = ""; + PlugInVersion = ""; } } /// - /// Gets or sets the CompileTime + /// Represents a ProgramInfoEventArgs /// - [JsonProperty("compileTime")] - public string CompileTime { get; set; } - - /// - /// Gets or sets the Include4Dat - /// - [JsonProperty("include4Dat")] - public string Include4Dat { get; set; } - - // SIMPL Windows properties - - /// - /// Gets or sets the SystemName - /// - [JsonProperty("systemName")] - public string SystemName { get; set; } - - /// - /// Gets or sets the CrestronDb - /// - [JsonProperty("crestronDb")] - public string CrestronDb { get; set; } - - /// - /// Gets or sets the Environment - /// - [JsonProperty("environment")] - public string Environment { get; set; } - - /// - /// Gets or sets the Programmer - /// - [JsonProperty("programmer")] - public string Programmer { get; set; } - - - // SSP Properties - - /// - /// Gets or sets the ApplicationName - /// - [JsonProperty("applicationName")] - public string ApplicationName { get; set; } - - /// - /// Gets or sets the ProgramTool - /// - [JsonProperty("programTool")] - public string ProgramTool { get; set; } - - /// - /// Gets or sets the MinFirmwareVersion - /// - [JsonProperty("minFirmwareVersion")] - public string MinFirmwareVersion { get; set; } - - /// - /// Gets or sets the PlugInVersion - /// - [JsonProperty("plugInVersion")] - public string PlugInVersion { get; set; } - - /// - /// Constructor - /// - /// program slot to get info about - public ProgramInfo(uint number) + public class ProgramInfoEventArgs : EventArgs { - ProgramNumber = number; + /// + /// Gets or sets the ProgramInfo + /// + public ProgramInfo ProgramInfo; - ProgramFile = ""; - FriendlyName = ""; - CompilerRevision = ""; - CompileTime = ""; - Include4Dat = ""; - - SystemName = ""; - CrestronDb = ""; - Environment = ""; - Programmer = ""; - - ApplicationName = ""; - ProgramTool = ""; - MinFirmwareVersion = ""; - PlugInVersion = ""; + /// + /// Constructor + /// + /// program info + public ProgramInfoEventArgs(ProgramInfo progInfo) + { + ProgramInfo = progInfo; + } } -} - -/// -/// Represents a ProgramInfoEventArgs -/// -public class ProgramInfoEventArgs : EventArgs -{ - /// - /// Gets or sets the ProgramInfo - /// - public ProgramInfo ProgramInfo; - - /// - /// Constructor - /// - /// program info - public ProgramInfoEventArgs(ProgramInfo progInfo) - { - ProgramInfo = progInfo; - } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs index 352d7cef..ee20a22a 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/EssentialsPartitionController.cs @@ -1,186 +1,228 @@ using PepperDash.Core; using System.Collections.Generic; -namespace PepperDash.Essentials.Core; - -/// -/// Represents an abstract controller device for a partition dividing rooms that are combinable -/// -/// In Auto mode, it can use a partition sensor to automatically determine whether the partition is present. -/// -/// In Manual mode it accepts user input to tell it whether the partition is present. -/// -public class EssentialsPartitionController : IPartitionController +namespace PepperDash.Essentials.Core { - private IPartitionStateProvider _partitionSensor; - - public bool IsInAutoMode { get; private set; } - - private bool _partitionPresent; - - public bool PartitionPresent + /// + /// Represents an abstract controller device for a partition dividing rooms that are combinable + /// + /// In Auto mode, it can use a partition sensor to automatically determine whether the partition is present. + /// + /// In Manual mode it accepts user input to tell it whether the partition is present. + /// + public class EssentialsPartitionController : IPartitionController { - get + private IPartitionStateProvider _partitionSensor; + + /// + /// Indicates whether the controller is in Auto mode or Manual mode + /// + public bool IsInAutoMode { get; private set; } + + private bool _partitionPresent; + + /// + /// Gets or sets the PartitionPresent state + /// + public bool PartitionPresent { - if (IsInAutoMode) + get { - return _partitionSensor.PartitionPresentFeedback.BoolValue; + if (IsInAutoMode) + { + return _partitionSensor.PartitionPresentFeedback.BoolValue; + } + + return _partitionPresent; } - - return _partitionPresent; - } - set - { - if (_partitionPresent == value) + set { - return; - } + if (_partitionPresent == value) + { + return; + } - _partitionPresent = value; + _partitionPresent = value; - if (PartitionPresentFeedback != null) - { - PartitionPresentFeedback.FireUpdate(); + if (PartitionPresentFeedback != null) + { + PartitionPresentFeedback.FireUpdate(); + } } } - } - public EssentialsPartitionController(string key, string name, IPartitionStateProvider sensor, bool defaultToManualMode, List adjacentRoomKeys) - { - Key = key; - - Name = name; - - AdjacentRoomKeys = adjacentRoomKeys; - - if (sensor != null) + /// + /// Constructor + /// + /// key for the partition controller + /// name of the partition controller + /// partition state provider sensor + /// whether to default to manual mode + /// list of adjacent room keys + public EssentialsPartitionController(string key, string name, IPartitionStateProvider sensor, bool defaultToManualMode, List adjacentRoomKeys) { - _partitionSensor = sensor; + Key = key; - if (!defaultToManualMode) + Name = name; + + AdjacentRoomKeys = adjacentRoomKeys; + + if (sensor != null) { - SetAutoMode(); + _partitionSensor = sensor; + + if (!defaultToManualMode) + { + SetAutoMode(); + } + else + { + SetManualMode(); + } } else { SetManualMode(); } - } - else - { - SetManualMode(); - } - PartitionPresentFeedback.FireUpdate(); - } - - private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - if (IsInAutoMode) - { - PartitionPresent = e.BoolValue; - } - } - - #region IPartitionController Members - - public List AdjacentRoomKeys { get; private set; } - - public void SetAutoMode() - { - Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Auto Mode", this); - - IsInAutoMode = true; - if (PartitionPresentFeedback != null) - { - PartitionPresentFeedback.SetValueFunc(() => _partitionSensor.PartitionPresentFeedback.BoolValue); - } - else - { - PartitionPresentFeedback = new BoolFeedback(() => _partitionSensor.PartitionPresentFeedback.BoolValue); - } - - if (_partitionSensor != null) - { - _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; - _partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; - PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; - } - - PartitionPresentFeedback.FireUpdate(); - } - - public void SetManualMode() - { - Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Manual Mode", this); - - IsInAutoMode = false; - if (PartitionPresentFeedback != null) - { - PartitionPresentFeedback.SetValueFunc(() => _partitionPresent); - } - else - { - PartitionPresentFeedback = new BoolFeedback(() => _partitionPresent); - } - - if (_partitionSensor != null) - { - _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; - PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; - } - - PartitionPresentFeedback.FireUpdate(); - } - - - public void SetPartitionStatePresent() - { - if (!IsInAutoMode) - { - PartitionPresent = true; PartitionPresentFeedback.FireUpdate(); } - } - public void SetPartitionStateNotPresent() - { - if (!IsInAutoMode) + private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) { - PartitionPresent = false; + if (IsInAutoMode) + { + PartitionPresent = e.BoolValue; + } + } + + #region IPartitionController Members + + /// + /// Gets or sets the AdjacentRoomKeys + /// + public List AdjacentRoomKeys { get; private set; } + + /// + /// SetAutoMode method + /// + public void SetAutoMode() + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Auto Mode", this); + + IsInAutoMode = true; + if (PartitionPresentFeedback != null) + { + PartitionPresentFeedback.SetValueFunc(() => _partitionSensor.PartitionPresentFeedback.BoolValue); + } + else + { + PartitionPresentFeedback = new BoolFeedback(() => _partitionSensor.PartitionPresentFeedback.BoolValue); + } + + if (_partitionSensor != null) + { + _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; + _partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; + PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; + } + PartitionPresentFeedback.FireUpdate(); } - } - public void ToggglePartitionState() - { - Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Toggling Partition State for {Key}", this); - Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"IsInAutoMode: {IsInAutoMode}", this); - - if (!IsInAutoMode) + /// + /// SetManualMode method + /// + public void SetManualMode() { - PartitionPresent = !PartitionPresent; + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Manual Mode", this); + + IsInAutoMode = false; + if (PartitionPresentFeedback != null) + { + PartitionPresentFeedback.SetValueFunc(() => _partitionPresent); + } + else + { + PartitionPresentFeedback = new BoolFeedback(() => _partitionPresent); + } + + if (_partitionSensor != null) + { + _partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange; + PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue; + } + PartitionPresentFeedback.FireUpdate(); } + + + /// + /// SetPartitionStatePresent method + /// + public void SetPartitionStatePresent() + { + if (!IsInAutoMode) + { + PartitionPresent = true; + PartitionPresentFeedback.FireUpdate(); + } + } + + /// + /// SetPartitionStateNotPresent method + /// + public void SetPartitionStateNotPresent() + { + if (!IsInAutoMode) + { + PartitionPresent = false; + PartitionPresentFeedback.FireUpdate(); + } + } + + /// + /// ToggglePartitionState method + /// + public void ToggglePartitionState() + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Toggling Partition State for {Key}", this); + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"IsInAutoMode: {IsInAutoMode}", this); + + if (!IsInAutoMode) + { + PartitionPresent = !PartitionPresent; + PartitionPresentFeedback.FireUpdate(); + } + } + + #endregion + + #region IPartitionStateProvider Members + + /// + /// Gets or sets the PartitionPresentFeedback + /// + public BoolFeedback PartitionPresentFeedback { get; private set; } + + #endregion + + #region IKeyName Members + + /// + /// Gets or sets the Name + /// + public string Name { get; private set; } + + #endregion + + #region IKeyed Members + + /// + /// Gets or sets the Key + /// + public string Key { get; private set; } + + #endregion } - - #endregion - - #region IPartitionStateProvider Members - - public BoolFeedback PartitionPresentFeedback { get; private set; } - - #endregion - - #region IKeyName Members - - public string Name { get; private set; } - - #endregion - - #region IKeyed Members - - public string Key { get; private set; } - - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/PartitionSensor/IPartitionStateProvider.cs b/src/PepperDash.Essentials.Core/PartitionSensor/IPartitionStateProvider.cs index 89f2a114..5bf40345 100644 --- a/src/PepperDash.Essentials.Core/PartitionSensor/IPartitionStateProvider.cs +++ b/src/PepperDash.Essentials.Core/PartitionSensor/IPartitionStateProvider.cs @@ -2,66 +2,66 @@ using Newtonsoft.Json; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IPartitionStateProvider -/// -public interface IPartitionStateProvider : IKeyName +namespace PepperDash.Essentials.Core { /// - /// Feedback indicating whether the partition is present + /// Defines the contract for IPartitionStateProvider /// - [JsonIgnore] - BoolFeedback PartitionPresentFeedback { get; } + public interface IPartitionStateProvider : IKeyName + { + /// + /// Feedback indicating whether the partition is present + /// + [JsonIgnore] + BoolFeedback PartitionPresentFeedback { get; } + + /// + /// Indicates whether the partition is present + /// + [JsonProperty("partitionPresent")] + bool PartitionPresent { get; } + } /// - /// Indicates whether the partition is present + /// Defines the contract for IPartitionController /// - [JsonProperty("partitionPresent")] - bool PartitionPresent { get; } -} + public interface IPartitionController : IPartitionStateProvider + { + /// + /// List of adjacent room keys + /// + [JsonProperty("adjacentRoomKeys")] + List AdjacentRoomKeys { get; } -/// -/// Defines the contract for IPartitionController -/// -public interface IPartitionController : IPartitionStateProvider -{ - /// - /// List of adjacent room keys - /// - [JsonProperty("adjacentRoomKeys")] - List AdjacentRoomKeys { get; } + /// + /// Indicates whether the controller is in Auto mode or Manual mode + /// + [JsonProperty("isInAutoMode")] + bool IsInAutoMode { get; } - /// - /// Indicates whether the controller is in Auto mode or Manual mode - /// - [JsonProperty("isInAutoMode")] - bool IsInAutoMode { get; } + /// + /// Sets the PartitionPresent state + /// + void SetPartitionStatePresent(); - /// - /// Sets the PartitionPresent state - /// - void SetPartitionStatePresent(); + /// + /// Sets the PartitionPresent state to not present + /// + void SetPartitionStateNotPresent(); - /// - /// Sets the PartitionPresent state to not present - /// - void SetPartitionStateNotPresent(); + /// + /// Toggles the PartitionPresent state + /// + void ToggglePartitionState(); - /// - /// Toggles the PartitionPresent state - /// - void ToggglePartitionState(); + /// + /// Sets the controller to Manual mode + /// + void SetManualMode(); - /// - /// Sets the controller to Manual mode - /// - void SetManualMode(); - - /// - /// Sets the controller to Auto mode - /// - void SetAutoMode(); -} + /// + /// Sets the controller to Auto mode + /// + void SetAutoMode(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj index 983155a5..b7f333bd 100644 --- a/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj +++ b/src/PepperDash.Essentials.Core/PepperDash.Essentials.Core.csproj @@ -1,9 +1,9 @@  - Debug;Release; + Debug;Release;Debug 4.7.2 - net8 + net472 true true bin\$(Configuration)\ @@ -25,8 +25,13 @@ bin\$(Configuration)\PepperDash_Essentials_Core.xml - - + + + + + + + diff --git a/src/PepperDash.Essentials.Core/PepperDash_Essentials_Core.csproj.DotSettings b/src/PepperDash.Essentials.Core/PepperDash_Essentials_Core.csproj.DotSettings new file mode 100644 index 00000000..cb991a69 --- /dev/null +++ b/src/PepperDash.Essentials.Core/PepperDash_Essentials_Core.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + False \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs b/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs index 116c4aa1..caad4b57 100644 --- a/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs +++ b/src/PepperDash.Essentials.Core/Plugins/IPluginDeviceFactory.cs @@ -1,29 +1,33 @@ -using PepperDash.Essentials.Core.Config; +using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core; - -/// -/// Defines a class that is capable of loading custom plugin device types -/// -public interface IPluginDeviceFactory : IDeviceFactory -{ +namespace PepperDash.Essentials.Core +{ /// - /// Required to define the minimum version for Essentials in the format xx.yy.zz + /// Defines a class that is capable of loading custom plugin device types /// - string MinimumEssentialsFrameworkVersion { get; } + public interface IPluginDeviceFactory : IDeviceFactory + { + /// + /// Required to define the minimum version for Essentials in the format xx.yy.zz + /// + string MinimumEssentialsFrameworkVersion { get; } + + } + + /// + /// Defines a class that is capable of loading custom plugin device types for development purposes + /// + [Obsolete("This interface is obsolete and will be removed in a future version." + + " Use IPluginDeviceFactory instead and check Global.IsRunningDevelopmentVersion to determine if the Essentials framework is in development mode.")] + public interface IPluginDevelopmentDeviceFactory : IPluginDeviceFactory + { + /// + /// Gets a list of all the development versions of the Essentials framework that are supported by this factory. + /// + List DevelopmentEssentialsFrameworkVersions { get; } + } } -/// -/// Defines a factory for creating plugin development devices, including support for specific framework versions. -/// -/// This interface extends to provide additional functionality -/// specific to plugin development, such as access to supported framework versions. -public interface IPluginDevelopmentDeviceFactory : IPluginDeviceFactory -{ - /// - /// Gets a list of framework versions that are essential for development. - /// - List DevelopmentEssentialsFrameworkVersions { get; } -} + diff --git a/src/PepperDash.Essentials.Core/Plugins/IncompatiblePlugin.cs b/src/PepperDash.Essentials.Core/Plugins/IncompatiblePlugin.cs deleted file mode 100644 index 8cce6f62..00000000 --- a/src/PepperDash.Essentials.Core/Plugins/IncompatiblePlugin.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Newtonsoft.Json; - - -namespace PepperDash.Essentials; - -/// -/// Represents a plugin that is incompatible with the current system or configuration. -/// -/// This class provides details about an incompatible plugin, including its name, the reason for the -/// incompatibility, and the plugin that triggered the incompatibility. The triggering plugin can be updated dynamically -/// using the method. -/// -/// -/// -public class IncompatiblePlugin(string name, string reason, string triggeredBy = null) -{ - /// - /// Gets the name associated with the object. - /// - [JsonProperty("name")] - public string Name { get; private set; } = name; - - /// - /// Gets the reason associated with the current operation or response. - /// - [JsonProperty("reason")] - public string Reason { get; private set; } = reason; - - /// - /// Gets the identifier of the entity or process that triggered the current operation. - /// - [JsonProperty("triggeredBy")] - public string TriggeredBy { get; private set; } = triggeredBy ?? "Direct load"; - - /// - /// Updates the name of the plugin that triggered the current operation. - /// - /// The name of the triggering plugin. Must not be null or empty. If the value is null or empty, the operation is - /// ignored. - public void UpdateTriggeringPlugin(string triggeringPlugin) - { - if (!string.IsNullOrEmpty(triggeringPlugin)) - { - TriggeredBy = triggeringPlugin; - } - } -} diff --git a/src/PepperDash.Essentials.Core/Plugins/LoadedAssembly.cs b/src/PepperDash.Essentials.Core/Plugins/LoadedAssembly.cs deleted file mode 100644 index 64c639e5..00000000 --- a/src/PepperDash.Essentials.Core/Plugins/LoadedAssembly.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Reflection; -using Newtonsoft.Json; - - -namespace PepperDash.Essentials; - -/// -/// Represents an assembly that has been loaded, including its name, version, and the associated instance. -/// -/// This class provides information about a loaded assembly, including its name and version as strings, -/// and the associated object. The assembly instance can be updated using the -/// method. -/// -/// -/// -public class LoadedAssembly(string name, string version, Assembly assembly) -{ - /// - /// Gets the name associated with the object. - /// - [JsonProperty("name")] - public string Name { get; private set; } = name; - - /// - /// Gets the version of the object as a string. - /// - [JsonProperty("version")] - public string Version { get; private set; } = version; - - /// - /// Gets the assembly associated with the current instance. - /// - [JsonIgnore] - public Assembly Assembly { get; private set; } = assembly; - - /// - /// Sets the assembly associated with the current instance. - /// - /// The to associate with the current instance. Cannot be . - public void SetAssembly(Assembly assembly) - { - Assembly = assembly; - } -} diff --git a/src/PepperDash.Essentials.Core/Plugins/Net8CompatibleAttribute.cs b/src/PepperDash.Essentials.Core/Plugins/Net8CompatibleAttribute.cs deleted file mode 100644 index 1a5b250a..00000000 --- a/src/PepperDash.Essentials.Core/Plugins/Net8CompatibleAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - - -namespace PepperDash.Essentials; - -/// -/// Indicates whether the assembly is compatible with .NET 8. -/// -/// This attribute is used to specify compatibility with .NET 8 for an assembly. By default, the -/// assembly is considered compatible unless explicitly marked otherwise. -/// A boolean value indicating whether the assembly is compatible with .NET 8. The default value is . -[AttributeUsage(AttributeTargets.Assembly)] -public class Net8CompatibleAttribute(bool isCompatible = true) : Attribute -{ - /// - /// Gets a value indicating whether the current object is compatible with the required conditions. - /// - public bool IsCompatible { get; } = isCompatible; -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs index c3259305..2f195ba9 100644 --- a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs +++ b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs @@ -1,564 +1,283 @@ using System; using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using System.Reflection; using System.IO; -using System.Reflection.PortableExecutable; -using System.Reflection.Metadata; - +using System.Linq; +using System.Reflection; +using Crestron.SimplSharp; +using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; - -namespace PepperDash.Essentials; -/// -/// Provides functionality for loading and managing plugins and assemblies in the application. -/// -/// The class is responsible for discovering, loading, and managing assemblies -/// and plugins, including handling compatibility checks for .NET 8. It supports loading assemblies from the program -/// directory, plugins folder, and .cplz archives. Additionally, it tracks incompatible plugins and provides reporting -/// capabilities for loaded assemblies and their versions. -public static class PluginLoader +namespace PepperDash.Essentials { /// - /// Gets the list of assemblies that have been loaded into the application. + /// Deals with loading plugins at runtime /// - public static List LoadedAssemblies { get; private set; } - - /// - /// Represents a collection of assemblies loaded from the plugin folder. - /// - /// This field is used to store assemblies that have been dynamically loaded from a designated - /// plugin folder. It is intended for internal use and should not be modified directly. - private static readonly List LoadedPluginFolderAssemblies; - - /// - /// Gets the list of plugins that are incompatible with the current system or configuration. - /// - /// This property provides information about plugins that are not supported or cannot function - /// correctly in the current environment. Use this list to identify and handle incompatible plugins appropriately in - /// your application logic. - public static List IncompatiblePlugins { get; private set; } - - /// - /// Gets the loaded assembly that contains the core functionality of the application. - /// - public static LoadedAssembly EssentialsAssembly { get; private set; } - - /// - /// Gets the loaded assembly information for the PepperDash Core library. - /// - public static LoadedAssembly PepperDashCoreAssembly { get; private set; } - - /// - /// Gets the list of assemblies that are Essentials plugins loaded by the application. - /// - public static List EssentialsPluginAssemblies { get; private set; } - - /// - /// Gets the directory path where plugins are stored. - /// - private static string PluginDirectory => Global.FilePathPrefix + "plugins"; - - /// - /// Gets the directory path where loaded plugin assemblies are stored. - /// - private static string LoadedPluginsDirectoryPath => PluginDirectory + Global.DirectorySeparator + "loadedAssemblies"; - - /// - /// Gets the path to the temporary directory used by the plugin. - /// - private static string TempDirectory => PluginDirectory + Global.DirectorySeparator + "temp"; - - /// - /// Represents a collection of fully qualified type names that are known to be incompatible with the current - /// application or framework. - /// - /// This collection contains the names of types that are deprecated, obsolete, or otherwise - /// incompatible with the intended usage of the application. These types may represent security risks, unsupported - /// features, or legacy APIs that should be avoided. - private static readonly HashSet KnownIncompatibleTypes = - [ - "System.Net.ICertificatePolicy", - "System.Security.Cryptography.SHA1CryptoServiceProvider", - "System.Web.HttpUtility", - "System.Configuration.ConfigurationManager", - "System.Web.Services.Protocols.SoapHttpClientProtocol", - "System.Runtime.Remoting", - "System.EnterpriseServices", - "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", - "System.Security.SecurityManager", - "System.Security.Permissions.FileIOPermission", - "System.AppDomain.CreateDomain" - ]; - - /// - /// Initializes static members of the class. - /// - /// This static constructor initializes the collections used to manage plugin assemblies and - /// track incompatible plugins. - static PluginLoader() + public static class PluginLoader { - LoadedAssemblies = []; - LoadedPluginFolderAssemblies = []; - EssentialsPluginAssemblies = []; - IncompatiblePlugins = []; - } + /// + /// The complete list of loaded assemblies. Includes Essentials Framework assemblies and plugins + /// + public static List LoadedAssemblies { get; private set; } - /// - /// Loads and registers assemblies from the application's directory that match specific naming patterns. - /// - /// This method scans the application's directory for assemblies with filenames containing - /// "Essentials" or "PepperDash" and registers them in the collection. It also - /// assigns specific assemblies to predefined properties, such as and , based on their names. Debug messages are logged at various stages to provide - /// detailed information about the process, including the number of assemblies found and their versions. This - /// method is intended to be used during application initialization to ensure required assemblies are loaded and - /// tracked. - public static void AddProgramAssemblies() - { - Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials"); - // Get the loaded assembly filenames - var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); - var assemblyFiles = appDi.GetFiles("*.dll"); + /// + /// The list of assemblies loaded from the plugins folder + /// + static List LoadedPluginFolderAssemblies; - Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length); + /// + /// The assembly for the Essentials Framework + /// + public static LoadedAssembly EssentialsAssembly { get; private set; } - foreach (var fi in assemblyFiles.Where(fi => fi.Name.Contains("Essentials") || fi.Name.Contains("PepperDash"))) + /// + /// The assembly for the PepperDash Core + /// + public static LoadedAssembly PepperDashCoreAssembly { get; private set; } + + /// + /// The list of assemblies loaded from the Essentials plugins folder + /// + public static List EssentialsPluginAssemblies { get; private set; } + + /// + /// The directory to look in for .cplz plugin packages + /// + static string _pluginDirectory => Global.FilePathPrefix + "plugins"; + + /// + /// The directory where plugins will be moved to and loaded from + /// + static string _loadedPluginsDirectoryPath => _pluginDirectory + Global.DirectorySeparator + "loadedAssemblies"; + + // The temp directory where .cplz archives will be unzipped to + static string _tempDirectory => _pluginDirectory + Global.DirectorySeparator + "temp"; + + + static PluginLoader() { - string version = string.Empty; - Assembly assembly = null; - - switch (fi.Name) - { - case ("PepperDashEssentials.dll"): - { - version = Global.AssemblyVersion; - EssentialsAssembly = new LoadedAssembly(fi.Name, version, assembly); - break; - } - case ("PepperDash_Essentials_Core.dll"): - { - version = Global.AssemblyVersion; - break; - } - case ("Essentials Devices Common.dll"): - { - version = Global.AssemblyVersion; - break; - } - case ("PepperDashCore.dll"): - { - Debug.LogMessage(LogEventLevel.Verbose, "Found PepperDash_Core.dll"); - version = Debug.PepperDashCoreVersion; - Debug.LogMessage(LogEventLevel.Verbose, "PepperDash_Core Version: {0}", version); - PepperDashCoreAssembly = new LoadedAssembly(fi.Name, version, assembly); - break; - } - } - - LoadedAssemblies.Add(new LoadedAssembly(fi.Name, version, assembly)); + LoadedAssemblies = new List(); + LoadedPluginFolderAssemblies = new List(); + EssentialsPluginAssemblies = new List(); } - if (Debug.Level > 1) + /// + /// AddProgramAssemblies method + /// + public static void AddProgramAssemblies() { - Debug.LogMessage(LogEventLevel.Verbose, "Loaded Assemblies:"); + Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials"); + // Get the loaded assembly filenames + var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); + var assemblyFiles = appDi.GetFiles("*.dll"); - foreach (var assembly in LoadedAssemblies) + Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length); + + foreach (var fi in assemblyFiles.Where(fi => fi.Name.Contains("Essentials") || fi.Name.Contains("PepperDash"))) { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly: {0}", assembly.Name); - } - } - } + string version = string.Empty; + Assembly assembly = null; - /// - /// Associates the specified assembly with the given name in the loaded assemblies collection. - /// - /// If an assembly with the specified name already exists in the loaded assemblies collection, - /// this method updates its associated assembly. If no matching name is found, the method does nothing. - /// The name used to identify the assembly. This value is case-sensitive and must not be null or empty. - /// The assembly to associate with the specified name. This value must not be null. - public static void AddLoadedAssembly(string name, Assembly assembly) - { - var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); - - loadedAssembly?.SetAssembly(assembly); - } - - /// - /// Determines whether a plugin assembly is compatible with .NET 8. - /// - /// This method analyzes the provided assembly to determine compatibility with .NET 8 by checking - /// for known incompatible types, inspecting custom attributes, and collecting assembly references. If the analysis - /// encounters an error, the method returns with an appropriate error message. - /// The file path to the plugin assembly to analyze. - /// A tuple containing the following: if the plugin - /// is compatible with .NET 8; otherwise, . A - /// string providing the reason for incompatibility, or if the plugin is - /// compatible. A list of assembly references found in the - /// plugin. - public static (bool IsCompatible, string Reason, List References) IsPluginCompatibleWithNet8(string filePath) - { - try - { - List referencedAssemblies = []; - - using FileStream fs = new(filePath, FileMode.Open, - FileAccess.Read, FileShare.ReadWrite); - using PEReader peReader = new(fs); - - if (!peReader.HasMetadata) - return (false, "Not a valid .NET assembly", referencedAssemblies); - - MetadataReader metadataReader = peReader.GetMetadataReader(); - - // Collect assembly references - foreach (var assemblyRefHandle in metadataReader.AssemblyReferences) - { - var assemblyRef = metadataReader.GetAssemblyReference(assemblyRefHandle); - string assemblyName = metadataReader.GetString(assemblyRef.Name); - referencedAssemblies.Add(assemblyName); - } - - // Check for references to known incompatible types - foreach (var typeRefHandle in metadataReader.TypeReferences) - { - var typeRef = metadataReader.GetTypeReference(typeRefHandle); - string typeNamespace = metadataReader.GetString(typeRef.Namespace); - string typeName = metadataReader.GetString(typeRef.Name); - - string fullTypeName = $"{typeNamespace}.{typeName}"; - if (KnownIncompatibleTypes.Contains(fullTypeName)) + switch (fi.Name) { - return (false, $"Uses incompatible type: {fullTypeName}", referencedAssemblies); + case ("PepperDashEssentials.dll"): + { + version = Global.AssemblyVersion; + EssentialsAssembly = new LoadedAssembly(fi.Name, version, assembly); + break; + } + case ("PepperDash_Essentials_Core.dll"): + { + version = Global.AssemblyVersion; + break; + } + case ("Essentials Devices Common.dll"): + { + version = Global.AssemblyVersion; + break; + } + case ("PepperDashCore.dll"): + { + Debug.LogMessage(LogEventLevel.Verbose, "Found PepperDash_Core.dll"); + version = Debug.PepperDashCoreVersion; + Debug.LogMessage(LogEventLevel.Verbose, "PepperDash_Core Version: {0}", version); + PepperDashCoreAssembly = new LoadedAssembly(fi.Name, version, assembly); + break; + } } + + LoadedAssemblies.Add(new LoadedAssembly(fi.Name, version, assembly)); } - // Check for explicit .NET 8 compatibility attribute - bool hasNet8Attribute = false; - foreach (var customAttributeHandle in metadataReader.GetAssemblyDefinition().GetCustomAttributes()) + if (Debug.Level > 1) { - var customAttribute = metadataReader.GetCustomAttribute(customAttributeHandle); - var ctorHandle = customAttribute.Constructor; + Debug.LogMessage(LogEventLevel.Verbose, "Loaded Assemblies:"); - if (ctorHandle.Kind == HandleKind.MemberReference) + foreach (var assembly in LoadedAssemblies) { - var memberRef = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle); - var typeRef = metadataReader.GetTypeReference((TypeReferenceHandle)memberRef.Parent); - - string typeName = metadataReader.GetString(typeRef.Name); - if (typeName == "Net8CompatibleAttribute" || typeName == "TargetFrameworkAttribute") - { - hasNet8Attribute = true; - break; - } - } - } - - if (hasNet8Attribute) - { - return (true, null, referencedAssemblies); - } - - // If we can't determine incompatibility, assume it's compatible - return (true, null, referencedAssemblies); - } - catch (Exception ex) - { - return (false, $"Error analyzing assembly: {ex.Message}", new List()); - } - } - - /// - /// Loads an assembly from the specified file path and verifies its compatibility with .NET 8. - /// - /// This method performs a compatibility check to ensure the assembly is compatible with .NET 8 - /// before attempting to load it. If the assembly is incompatible, it is added to the list of incompatible plugins, - /// and a warning is logged. If the assembly is already loaded, the existing instance is returned instead of - /// reloading it. Exceptions during the load process are handled internally, and appropriate log messages are - /// generated for issues such as: Incompatibility with .NET - /// 8. File load conflicts (e.g., an assembly with the same name is already - /// loaded). General errors, including potential .NET Framework - /// compatibility issues. - /// The full path to the assembly file to load. This cannot be null or empty. - /// An optional identifier for the entity requesting the load operation. This can be null. - /// A object representing the loaded assembly if the operation succeeds; otherwise, - /// . - private static LoadedAssembly LoadAssembly(string filePath, string requestedBy = null) - { - try - { - // Check .NET 8 compatibility before loading - var (isCompatible, reason, references) = IsPluginCompatibleWithNet8(filePath); - if (!isCompatible) - { - string fileName = Path.GetFileName(filePath); - Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason); - - var incompatiblePlugin = new IncompatiblePlugin(fileName, reason, requestedBy); - IncompatiblePlugins.Add(incompatiblePlugin); - return null; - } - - var assembly = Assembly.LoadFrom(filePath); - if (assembly != null) - { - var assyVersion = GetAssemblyVersion(assembly); - var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly); - LoadedAssemblies.Add(loadedAssembly); - Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); - return loadedAssembly; - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); - } - return null; - } - catch(FileLoadException ex) when (ex.Message.Contains("Assembly with same name is already loaded")) - { - // Get the assembly name from the file path - string assemblyName = Path.GetFileNameWithoutExtension(filePath); - - // Try to find the already loaded assembly - var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.OrdinalIgnoreCase)); - - if (existingAssembly != null) - { - Debug.LogMessage(LogEventLevel.Information, "Assembly '{0}' is already loaded, using existing instance", assemblyName); - var assyVersion = GetAssemblyVersion(existingAssembly); - var loadedAssembly = new LoadedAssembly(existingAssembly.GetName().Name, assyVersion, existingAssembly); - LoadedAssemblies.Add(loadedAssembly); - return loadedAssembly; - } - - Debug.LogMessage(LogEventLevel.Warning, "Assembly with same name already loaded but couldn't find it: {0}", filePath); - return null; - } - catch(Exception ex) - { - string fileName = Path.GetFileName(filePath); - - // Check if this might be a .NET Framework compatibility issue - if (ex.Message.Contains("Could not load type") || - ex.Message.Contains("Unable to load one or more of the requested types")) - { - Debug.LogMessage(LogEventLevel.Error, "Error loading assembly {0}: Likely .NET 8 compatibility issue: {1}", - fileName, ex.Message); - IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy)); - } - else - { - Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); - } - return null; - } - } - - /// - /// Retrieves the version information of the specified assembly. - /// - /// This method first attempts to retrieve the version from the . If the attribute is not present, it falls back to the assembly's - /// version as defined in its metadata. - /// The assembly from which to retrieve the version information. Cannot be . - /// A string representing the version of the assembly. If the assembly has an , its value is returned. Otherwise, the assembly's - /// version is returned in the format "Major.Minor.Build.Revision". - public static string GetAssemblyVersion(Assembly assembly) - { - var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); - if (ver != null && ver.Length > 0) - { - // Get the AssemblyInformationalVersion - AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute; - return verAttribute.InformationalVersion; - } - else - { - // Get the AssemblyVersion - var version = assembly.GetName().Version; - var verStr = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision); - return verStr; - } - } - - /// - /// Determines whether an assembly with the specified name is currently loaded. - /// - /// This method performs a case-sensitive comparison to determine if the specified assembly is - /// loaded. It logs verbose messages indicating the status of the check. - /// The name of the assembly to check. This value is case-sensitive. - /// if an assembly with the specified name is loaded; otherwise, . - public static bool CheckIfAssemblyLoaded(string name) - { - Debug.LogMessage(LogEventLevel.Verbose, "Checking if assembly: {0} is loaded...", name); - var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name)); - - if (loadedAssembly != null) - { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly already loaded."); - return true; - } - else - { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly not loaded."); - return false; - } - } - - /// - /// Reports the versions of the Essentials framework, PepperDash Core, and loaded plugins to the console. - /// - /// This method outputs version information for the Essentials framework, PepperDash Core, and - /// all loaded Essentials plugins to the Crestron console. If any incompatible plugins are detected, their details - /// are also reported, including the reason for incompatibility and the plugin that required them, if - /// applicable. - /// The command string that triggered the version report. This parameter is not used directly by the method. - public static void ReportAssemblyVersions(string command) - { - CrestronConsole.ConsoleCommandResponse("Essentials Version: {0}" + CrestronEnvironment.NewLine, Global.AssemblyVersion); - CrestronConsole.ConsoleCommandResponse("PepperDash Core Version: {0}" + CrestronEnvironment.NewLine, PepperDashCoreAssembly.Version); - CrestronConsole.ConsoleCommandResponse("Essentials Plugin Versions:" + CrestronEnvironment.NewLine); - foreach (var assembly in EssentialsPluginAssemblies) - { - CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version); - } - - if (IncompatiblePlugins.Count > 0) - { - CrestronConsole.ConsoleCommandResponse("Incompatible Plugins:" + CrestronEnvironment.NewLine); - foreach (var plugin in IncompatiblePlugins) - { - if (plugin.TriggeredBy != "Direct load") - { - CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine, - plugin.Name, plugin.Reason, plugin.TriggeredBy); - } - else - { - CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine, - plugin.Name, plugin.Reason); + Debug.LogMessage(LogEventLevel.Verbose, "Assembly: {0}", assembly.Name); } } } - } - /// - /// Moves .dll assemblies from the plugins folder to the loaded plugins directory. - /// - /// This method scans the plugins folder for .dll files and moves them to the loaded plugins - /// directory if they are not already loaded. If a file with the same name exists in the target directory, it is - /// replaced. The method logs the process at various stages and handles exceptions for individual files to ensure - /// the operation continues for other files. - private static void MoveDllAssemblies() - { - Debug.LogMessage(LogEventLevel.Information, "Looking for .dll assemblies from plugins folder..."); - var pluginDi = new DirectoryInfo(PluginDirectory); - var pluginFiles = pluginDi.GetFiles("*.dll"); - - if (pluginFiles.Length > 0) + /// + /// SetEssentialsAssembly method + /// + public static void SetEssentialsAssembly(string name, Assembly assembly) { - if (!Directory.Exists(LoadedPluginsDirectoryPath)) + var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); + + if (loadedAssembly != null) { - Directory.CreateDirectory(LoadedPluginsDirectoryPath); + loadedAssembly.SetAssembly(assembly); } } - foreach (var pluginFile in pluginFiles) + /// + /// Loads an assembly via Reflection and adds it to the list of loaded assemblies + /// + /// + static LoadedAssembly LoadAssembly(string filePath) { try { - Debug.LogMessage(LogEventLevel.Information, "Found .dll: {0}", pluginFile.Name); - - if (!CheckIfAssemblyLoaded(pluginFile.Name)) + //Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load {0}", filePath); + var assembly = Assembly.LoadFrom(filePath); + if (assembly != null) { - string filePath = string.Empty; + var assyVersion = GetAssemblyVersion(assembly); - filePath = LoadedPluginsDirectoryPath + Global.DirectorySeparator + pluginFile.Name; - - // Check if there is a previous file in the loadedPlugins directory and delete - if (File.Exists(filePath)) - { - Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath); - File.Delete(filePath); - } - - // Move the file - File.Move(pluginFile.FullName, filePath); - Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath); + var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly); + LoadedAssemblies.Add(loadedAssembly); + Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); + return loadedAssembly; } else { - Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", pluginFile.FullName); + Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); + } + + return null; + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); + return null; + } + + } + + /// + /// Attempts to get the assembly informational version and if not possible gets the version + /// + /// + /// + public static string GetAssemblyVersion(Assembly assembly) + { + var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); + if (ver != null && ver.Length > 0) + { + // Get the AssemblyInformationalVersion + AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute; + return verAttribute.InformationalVersion; + } + else + { + // Get the AssemblyVersion + var version = assembly.GetName().Version; + var verStr = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision); + return verStr; + } + } + + /// + /// Checks if the filename matches an already loaded assembly file's name + /// + /// name of loaded assembly + /// True if file already matches loaded assembly file. + public static bool CheckIfAssemblyLoaded(string name) + { + Debug.LogMessage(LogEventLevel.Verbose, "Checking if assembly: {0} is loaded...", name); + var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name)); + + if (loadedAssembly != null) + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly already loaded."); + return true; + } + else + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly not loaded."); + return false; + } + } + + /// + /// Associates the specified assembly with the given name in the loaded assemblies collection. + /// + /// If an assembly with the specified name already exists in the loaded assemblies collection, + /// this method updates its associated assembly. If no matching name is found, the method does nothing. + /// The name used to identify the assembly. This value is case-sensitive and must not be null or empty. + /// The assembly to associate with the specified name. This value must not be null. + public static void AddLoadedAssembly(string name, Assembly assembly) + { + var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); + + loadedAssembly?.SetAssembly(assembly); + } + + /// + /// Used by console command to report the currently loaded assemblies and versions + /// + /// + public static void ReportAssemblyVersions(string command) + { + CrestronConsole.ConsoleCommandResponse("Essentials Version: {0}" + CrestronEnvironment.NewLine, Global.AssemblyVersion); + CrestronConsole.ConsoleCommandResponse("PepperDash Core Version: {0}" + CrestronEnvironment.NewLine, PepperDashCoreAssembly.Version); + CrestronConsole.ConsoleCommandResponse("Essentials Plugin Versions:" + CrestronEnvironment.NewLine); + foreach (var assembly in EssentialsPluginAssemblies) + { + CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version); + } + + //CrestronConsole.ConsoleCommandResponse("Loaded Assemblies:" + CrestronEnvironment.NewLine); + //foreach (var assembly in LoadedAssemblies) + //{ + // CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version); + //} + } + + /// + /// Moves any .dll assemblies not already loaded from the plugins folder to loadedPlugins folder + /// + static void MoveDllAssemblies() + { + Debug.LogMessage(LogEventLevel.Information, "Looking for .dll assemblies from plugins folder..."); + + var pluginDi = new DirectoryInfo(_pluginDirectory); + var pluginFiles = pluginDi.GetFiles("*.dll"); + + if (pluginFiles.Length > 0) + { + if (!Directory.Exists(_loadedPluginsDirectoryPath)) + { + Directory.CreateDirectory(_loadedPluginsDirectoryPath); } } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, "Error with plugin file {0} . Exception: {1}", pluginFile.FullName, e); - continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here - } - } - Debug.LogMessage(LogEventLevel.Information, "Done with .dll assemblies"); - } - - /// - /// Extracts and processes .cplz archive files found in the specified directories, moving their contents to the - /// appropriate plugin directory. - /// - /// This method searches for .cplz files in the plugin directory and user folder, extracts their - /// contents, and moves any .dll files to the loaded plugins directory. If a .dll file with the same name already - /// exists in the target directory, it is replaced with the new file. Temporary files and directories created during - /// the process are cleaned up after the operation completes. - private static void UnzipAndMoveCplzArchives() - { - Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from plugins folder..."); - var di = new DirectoryInfo(PluginDirectory); - var zFiles = di.GetFiles("*.cplz"); - - //// Find cplz files at the root of the user folder. Makes development/testing easier for VC-4, and helps with mistakes by end users - - //var userDi = new DirectoryInfo(Global.FilePathPrefix); - //var userZFiles = userDi.GetFiles("*.cplz"); - - Debug.LogInformation("Checking {folder} for .cplz files", Global.FilePathPrefix); - var cplzFiles = Directory.GetFiles(Global.FilePathPrefix, "*.cplz", SearchOption.AllDirectories) - .Select(f => new FileInfo(f)) - .ToArray(); - - if (cplzFiles.Length > 0) - { - if (!Directory.Exists(LoadedPluginsDirectoryPath)) - { - Directory.CreateDirectory(LoadedPluginsDirectoryPath); - } - } - - foreach (var zfi in cplzFiles) - { - Directory.CreateDirectory(TempDirectory); - var tempDi = new DirectoryInfo(TempDirectory); - - Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.FullName); - var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName); - Debug.LogMessage(LogEventLevel.Information, "UnZip Result: {0}", result.ToString()); - - var tempFiles = tempDi.GetFiles("*.dll"); - foreach (var tempFile in tempFiles) + foreach (var pluginFile in pluginFiles) { try { - if (!CheckIfAssemblyLoaded(tempFile.Name)) + Debug.LogMessage(LogEventLevel.Information, "Found .dll: {0}", pluginFile.Name); + + if (!CheckIfAssemblyLoaded(pluginFile.Name)) { string filePath = string.Empty; - filePath = LoadedPluginsDirectoryPath + Global.DirectorySeparator + tempFile.Name; + filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + pluginFile.Name; // Check if there is a previous file in the loadedPlugins directory and delete if (File.Exists(filePath)) @@ -568,348 +287,315 @@ public static class PluginLoader } // Move the file - File.Move(tempFile.FullName, filePath); - Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath); + File.Move(pluginFile.FullName, filePath); + Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath); } else { - Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", tempFile.FullName); + Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", pluginFile.FullName); } } catch (Exception e) { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly {0} is not a custom assembly. Exception: {1}", tempFile.FullName, e); + Debug.LogMessage(LogEventLevel.Verbose, "Error with plugin file {0} . Exception: {1}", pluginFile.FullName, e); continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here } } - // Delete the .cplz and the temp directory - Directory.Delete(TempDirectory, true); - zfi.Delete(); + Debug.LogMessage(LogEventLevel.Information, "Done with .dll assemblies"); } - Debug.LogMessage(LogEventLevel.Information, "Done with .cplz archives"); - } - - /// - /// Loads plugin assemblies from the designated plugin directory, checks their compatibility with .NET 8, and loads - /// the compatible assemblies into the application. - /// - /// This method scans the plugin directory for all `.dll` files, verifies their compatibility - /// with .NET 8, and attempts to load the compatible assemblies. Assemblies that are incompatible are logged as - /// warnings and added to a list of incompatible plugins for further inspection. - private static void LoadPluginAssemblies() - { - Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder..."); - var pluginDi = new DirectoryInfo(LoadedPluginsDirectoryPath); - var pluginFiles = pluginDi.GetFiles("*.dll"); - - Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length); - - // First, check compatibility of all assemblies before loading any - var assemblyCompatibility = new Dictionary References)>(); - - foreach (var pluginFile in pluginFiles) + /// + /// Unzips each .cplz archive into the temp directory and moves any unloaded files into loadedPlugins + /// + static void UnzipAndMoveCplzArchives() { - string fileName = pluginFile.Name; - assemblyCompatibility[fileName] = IsPluginCompatibleWithNet8(pluginFile.FullName); - } - - // Now load compatible assemblies and track incompatible ones - foreach (var pluginFile in pluginFiles) - { - string fileName = pluginFile.Name; - var (isCompatible, reason, _) = assemblyCompatibility[fileName]; - - if (!isCompatible) + Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from user folder..."); + //var di = new DirectoryInfo(_pluginDirectory); + //var zFiles = di.GetFiles("*.cplz"); + + //// Find cplz files at the root of the user folder. Makes development/testing easier for VC-4, and helps with mistakes by end users + + //var userDi = new DirectoryInfo(Global.FilePathPrefix); + //var userZFiles = userDi.GetFiles("*.cplz"); + + Debug.LogInformation("Checking {folder} for .cplz files", Global.FilePathPrefix); + var cplzFiles = Directory.GetFiles(Global.FilePathPrefix, "*.cplz", SearchOption.AllDirectories) + .Select(f => new FileInfo(f)) + .ToArray(); + + if (cplzFiles.Length > 0) { - Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason); - IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, reason, null)); - continue; + if (!Directory.Exists(_loadedPluginsDirectoryPath)) + { + Directory.CreateDirectory(_loadedPluginsDirectoryPath); + } } - - // Try to load the assembly - var loadedAssembly = LoadAssembly(pluginFile.FullName, null); - - if (loadedAssembly != null) + + foreach (var zfi in cplzFiles) { - LoadedPluginFolderAssemblies.Add(loadedAssembly); - } - } + Directory.CreateDirectory(_tempDirectory); + var tempDi = new DirectoryInfo(_tempDirectory); - Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded."); - } + Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.FullName); + var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName); + Debug.LogMessage(LogEventLevel.Information, "UnZip Result: {0}", result.ToString()); - /// - /// Loads and initializes custom plugin types from the assemblies in the plugin folder. - /// - /// This method iterates through all loaded plugin assemblies, identifies types that implement - /// the interface, and attempts to instantiate and load them. Assemblies or - /// types that cannot be loaded due to missing dependencies, type loading errors, or other exceptions are logged, - /// and incompatible plugins are tracked for further analysis. - private static void LoadCustomPluginTypes() - { - Debug.LogMessage(LogEventLevel.Information, "Loading Custom Plugin Types..."); - - foreach (var loadedAssembly in LoadedPluginFolderAssemblies) - { - // Skip if assembly is null (can happen if we had loading issues) - if (loadedAssembly == null || loadedAssembly.Assembly == null) - continue; - - // iteratate this assembly's classes, looking for "LoadPlugin()" methods - try - { - var assy = loadedAssembly.Assembly; - Type[] types = []; - try - { - types = assy.GetTypes(); - Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}"); - } - catch (ReflectionTypeLoadException e) - { - Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}", - loadedAssembly.Name, e.Message); - - // Check if any of the loader exceptions are due to missing assemblies - foreach (var loaderEx in e.LoaderExceptions) - { - if (loaderEx is FileNotFoundException fileNotFoundEx) - { - string missingAssembly = fileNotFoundEx.FileName; - if (!string.IsNullOrEmpty(missingAssembly)) - { - Debug.LogMessage(LogEventLevel.Warning, "Assembly {0} requires missing dependency: {1}", - loadedAssembly.Name, missingAssembly); - - // Add to incompatible plugins with dependency information - IncompatiblePlugins.Add(new IncompatiblePlugin( - Path.GetFileName(missingAssembly), - $"Missing dependency required by {loadedAssembly.Name}", - loadedAssembly.Name)); - } - } - } - - Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); - continue; - } - catch (TypeLoadException e) - { - Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}", - loadedAssembly.Name, e.Message); - Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); - - // Add to incompatible plugins if this is likely a .NET 8 compatibility issue - if (e.Message.Contains("Could not load type") || - e.Message.Contains("Unable to load one or more of the requested types")) - { - IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, - $"Type loading error: {e.Message}", - null)); - } - - continue; - } - - foreach (var type in types) + var tempFiles = tempDi.GetFiles("*.dll"); + foreach (var tempFile in tempFiles) { try { - if (!typeof(IPluginDeviceFactory).IsAssignableFrom(type) || type.IsAbstract) + if (!CheckIfAssemblyLoaded(tempFile.Name)) { + string filePath = string.Empty; + + filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + tempFile.Name; + + // Check if there is a previous file in the loadedPlugins directory and delete + if (File.Exists(filePath)) + { + Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath); + File.Delete(filePath); + } + + // Move the file + File.Move(tempFile.FullName, filePath); + Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", tempFile.FullName); + } + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly {0} is not a custom assembly. Exception: {1}", tempFile.FullName, e); + continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here + } + } + + // Delete the .cplz and the temp directory + Directory.Delete(_tempDirectory, true); + zfi.Delete(); + } + + Debug.LogMessage(LogEventLevel.Information, "Done with .cplz archives"); + } + + /// + /// Attempts to load the assemblies from the loadedPlugins folder + /// + static void LoadPluginAssemblies() + { + Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder..."); + var pluginDi = new DirectoryInfo(_loadedPluginsDirectoryPath); + var pluginFiles = pluginDi.GetFiles("*.dll"); + + Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length); + + foreach (var pluginFile in pluginFiles) + { + var loadedAssembly = LoadAssembly(pluginFile.FullName); + + LoadedPluginFolderAssemblies.Add(loadedAssembly); + } + + Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded."); + } + + /// + /// Iterate the loaded assemblies and try to call the LoadPlugin method + /// + static void LoadCustomPluginTypes() + { + Debug.LogMessage(LogEventLevel.Information, "Loading Custom Plugin Types..."); + foreach (var loadedAssembly in LoadedPluginFolderAssemblies) + { + // iteratate this assembly's classes, looking for "LoadPlugin()" methods + try + { + var assy = loadedAssembly.Assembly; + Type[] types = { }; + try + { + types = assy.GetTypes(); + Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}"); + } + catch (TypeLoadException e) + { + Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}", + loadedAssembly.Name, e.Message); + Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); + continue; + } + + foreach (var type in types) + { + try + { + if (typeof(IPluginDeviceFactory).IsAssignableFrom(type) && !type.IsAbstract) + { + var plugin = + (IPluginDeviceFactory)Activator.CreateInstance(type); + LoadCustomPlugin(plugin, loadedAssembly); + } + } + catch (NotSupportedException) + { + //this happens for dlls that aren't PD dlls, like ports of Mono classes into S#. Swallowing. + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Error, "Load Plugin not found. {0}.{2} is not a plugin factory. Exception: {1}", + loadedAssembly.Name, e.Message, type.Name); continue; } - var plugin = (IPluginDeviceFactory)Activator.CreateInstance(type); - LoadCustomPlugin(plugin, loadedAssembly); - } - catch (NotSupportedException) - { - //this happens for dlls that aren't PD dlls, like ports of Mono classes into S#. Swallowing. - } - catch (Exception ex) - { - Debug.LogError("Load Plugin not found. {assemblyName}.{typeName} is not a plugin factory. Exception: {exception}", - loadedAssembly.Name, type.Name, ex.Message); - continue; } } - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Error Loading assembly {0}: {1}", - loadedAssembly.Name, e.Message); - Debug.LogMessage(LogEventLevel.Verbose, "{0}", e.StackTrace); - - // Add to incompatible plugins if this is likely a .NET 8 compatibility issue - if (e.Message.Contains("Could not load type") || - e.Message.Contains("Unable to load one or more of the requested types")) + catch (Exception e) { - IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, - $"Assembly loading error: {e.Message}", - null)); + Debug.LogMessage(LogEventLevel.Information, "Error Loading assembly {0}: {1}", + loadedAssembly.Name, e.Message); + Debug.LogMessage(LogEventLevel.Verbose, "{0}", e.StackTrace); + continue; } - - continue; + } + // plugin dll will be loaded. Any classes in plugin should have a static constructor + // that registers that class with the Core.DeviceFactory + Debug.LogMessage(LogEventLevel.Information, "Done Loading Custom Plugin Types."); + } + + /// + /// Loads a custom plugin and performs a dependency check to ensure compatibility with the required Essentials + /// framework version. + /// + /// This method verifies that the plugin meets the minimum required Essentials framework version + /// before loading it. If the plugin fails the dependency check, it is skipped, and a log message is generated. If + /// the plugin passes the check, it is loaded, and its type factories are initialized. + /// The plugin to be loaded, implementing the interface. If the plugin also + /// implements , additional checks for development versions are + /// performed. + /// The assembly associated with the plugin being loaded. This is used for logging and tracking purposes. + static void LoadCustomPlugin(IPluginDeviceFactory pluginDeviceFactory, LoadedAssembly loadedAssembly) + { + var passed = pluginDeviceFactory is IPluginDevelopmentDeviceFactory developmentPlugin ? Global.IsRunningDevelopmentVersion + (developmentPlugin.DevelopmentEssentialsFrameworkVersions, developmentPlugin.MinimumEssentialsFrameworkVersion) + : Global.IsRunningMinimumVersionOrHigher(pluginDeviceFactory.MinimumEssentialsFrameworkVersion); + + if (!passed) + { + Debug.LogMessage(LogEventLevel.Information, + "\r\n********************\r\n\tPlugin indicates minimum Essentials version {0}. Dependency check failed. Skipping Plugin {1}\r\n********************", + pluginDeviceFactory.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); + return; + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Passed plugin passed dependency check (required version {0})", pluginDeviceFactory.MinimumEssentialsFrameworkVersion); + } + + Debug.LogMessage(LogEventLevel.Information, "Loading plugin factory: {0}", loadedAssembly.Name); + + LoadDeviceFactories(pluginDeviceFactory); + + if (!EssentialsPluginAssemblies.Contains(loadedAssembly)) + EssentialsPluginAssemblies.Add(loadedAssembly); + } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IPluginDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) + { + string description = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0 + ? descriptionAttribute[0].Description + : "No description available"; + + DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); } } - - // Update incompatible plugins with dependency information - var pluginDependencies = new Dictionary>(); - // Populate pluginDependencies with relevant data - // Example: pluginDependencies["PluginA"] = new List { "Dependency1", "Dependency2" }; - UpdateIncompatiblePluginDependencies(pluginDependencies); - - // plugin dll will be loaded. Any classes in plugin should have a static constructor - // that registers that class with the Core.DeviceFactory - Debug.LogMessage(LogEventLevel.Information, "Done Loading Custom Plugin Types."); - } - /// - /// Updates the triggering plugin information for incompatible plugins based on their dependencies. - /// - /// This method iterates through a predefined list of incompatible plugins and updates their - /// triggering plugin information if they were directly loaded and are found to be dependencies of other plugins. - /// The update is performed for the first plugin that depends on the incompatible plugin. - /// A dictionary where the key is the name of a plugin and the value is a list of its dependencies. Each dependency - /// is represented as a string, which may include additional metadata. - private static void UpdateIncompatiblePluginDependencies(Dictionary> pluginDependencies) - { - // For each incompatible plugin - foreach (var incompatiblePlugin in IncompatiblePlugins) + + /// + /// LoadPlugins method + /// + public static void LoadPlugins() { - // If it already has a requestedBy, skip it - if (incompatiblePlugin.TriggeredBy != "Direct load") - continue; - - // Find plugins that depend on this incompatible plugin - foreach (var plugin in pluginDependencies) + Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", _pluginDirectory); + + if (Directory.Exists(_pluginDirectory)) { - string pluginName = plugin.Key; - List dependencies = plugin.Value; - - // If this plugin depends on the incompatible plugin - if (dependencies.Contains(incompatiblePlugin.Name) || - dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ","))) + Debug.LogMessage(LogEventLevel.Information, "Plugins directory found, checking for plugins"); + + // Deal with any .dll files + MoveDllAssemblies(); + + // Deal with any .cplz files + UnzipAndMoveCplzArchives(); + + if (Directory.Exists(_loadedPluginsDirectoryPath)) { - incompatiblePlugin.UpdateTriggeringPlugin(pluginName); - break; + // Load the assemblies from the loadedPlugins folder into the AppDomain + LoadPluginAssemblies(); + + // Load the types from any custom plugin assemblies + LoadCustomPluginTypes(); } } } - } - - /// - /// Loads a custom plugin and performs a dependency check to ensure compatibility with the required Essentials - /// framework version. - /// - /// This method verifies that the plugin meets the minimum required Essentials framework version - /// before loading it. If the plugin fails the dependency check, it is skipped, and a log message is generated. If - /// the plugin passes the check, it is loaded, and its type factories are initialized. - /// The plugin to be loaded, implementing the interface. If the plugin also - /// implements , additional checks for development versions are - /// performed. - /// The assembly associated with the plugin being loaded. This is used for logging and tracking purposes. - private static void LoadCustomPlugin(IPluginDeviceFactory deviceFactory, LoadedAssembly loadedAssembly) - { - var developmentDeviceFactory = deviceFactory as IPluginDevelopmentDeviceFactory; - var passed = developmentDeviceFactory != null ? Global.IsRunningDevelopmentVersion - (developmentDeviceFactory.DevelopmentEssentialsFrameworkVersions, developmentDeviceFactory.MinimumEssentialsFrameworkVersion) - : Global.IsRunningMinimumVersionOrHigher(deviceFactory.MinimumEssentialsFrameworkVersion); - - if (!passed) - { - Debug.LogInformation( - "\r\n********************\r\n\tPlugin indicates minimum Essentials version {minimumEssentialsVersion}. Dependency check failed. Skipping Plugin {pluginName}\r\n********************", - deviceFactory.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); - return; - } - else - { - Debug.LogInformation("Passed plugin passed dependency check (required version {essentialsMinimumVersion})", deviceFactory.MinimumEssentialsFrameworkVersion); - } - - Debug.LogInformation("Loading plugin: {pluginName}", loadedAssembly.Name); - - LoadDeviceFactories(deviceFactory); - - if(!EssentialsPluginAssemblies.Contains(loadedAssembly)) - EssentialsPluginAssemblies.Add(loadedAssembly); } /// - /// Loads device factories from the specified plugin device factory and registers them for use. + /// Represents a LoadedAssembly /// - /// This method retrieves metadata from the provided , including - /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type - /// names are converted to lowercase for registration. - /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. - private static void LoadDeviceFactories(IPluginDeviceFactory deviceFactory) + public class LoadedAssembly { - foreach (var typeName in deviceFactory.TypeNames) + /// + /// Gets the name of the assembly + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the version of the assembly + /// + [JsonProperty("version")] + public string Version { get; private set; } + + /// + /// Gets the assembly + /// + [JsonIgnore] + public Assembly Assembly { get; private set; } + + /// + /// Initializes a new instance of the LoadedAssembly class + /// + /// The name of the assembly + /// The version of the assembly + /// The assembly + public LoadedAssembly(string name, string version, Assembly assembly) { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + Name = name; + Version = version; + Assembly = assembly; + } + + /// + /// SetAssembly method + /// + public void SetAssembly(Assembly assembly) + { + Assembly = assembly; } } - - /// - /// Loads plugins from the designated plugin directory, processes them, and integrates them into the application. - /// - /// This method performs the following steps: Checks if - /// the plugin directory exists. Processes any plugin files, including .dll - /// and .cplz files, by moving or extracting them as needed. Loads - /// assemblies from the processed plugins into the application domain. - /// Identifies and reports any incompatible plugins, including the reason for - /// incompatibility. Plugins that are successfully loaded are made available for use, - /// while incompatible plugins are logged for review. - public static void LoadPlugins() - { - Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", PluginDirectory); - - if (Directory.Exists(PluginDirectory)) - { - Debug.LogMessage(LogEventLevel.Information, "Plugins directory found, checking for plugins"); - - // Deal with any .dll files - MoveDllAssemblies(); - - // Deal with any .cplz files - UnzipAndMoveCplzArchives(); - - if (Directory.Exists(LoadedPluginsDirectoryPath)) - { - // Load the assemblies from the loadedPlugins folder into the AppDomain - LoadPluginAssemblies(); - - // Load the types from any custom plugin assemblies - LoadCustomPluginTypes(); - } - - // Report on incompatible plugins - if (IncompatiblePlugins.Count > 0) - { - Debug.LogMessage(LogEventLevel.Warning, "Found {0} incompatible plugins:", IncompatiblePlugins.Count); - foreach (var plugin in IncompatiblePlugins) - { - if (plugin.TriggeredBy != "Direct load") - { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})", - plugin.Name, plugin.Reason, plugin.TriggeredBy); - } - else - { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}", - plugin.Name, plugin.Reason); - } - } - } - } - } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Presets/DevicePresets.cs b/src/PepperDash.Essentials.Core/Presets/DevicePresets.cs index 4b009e45..253a19e1 100644 --- a/src/PepperDash.Essentials.Core/Presets/DevicePresets.cs +++ b/src/PepperDash.Essentials.Core/Presets/DevicePresets.cs @@ -1,64 +1,295 @@ - + using System; using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Newtonsoft.Json; using PepperDash.Core; +//using SSMono.IO; +using PepperDash.Core.WebApi.Presets; using Serilog.Events; -namespace PepperDash.Essentials.Core.Presets; - -/// -/// Class that represents the model behind presets display -/// -public class DevicePresetsModel : Device +namespace PepperDash.Essentials.Core.Presets { /// - /// Delegate for PresetRecalled event, which is fired when a preset is recalled. Provides the device and channel that was recalled. + /// Class that represents the model behind presets display /// - /// - /// - public delegate void PresetRecalledCallback(ISetTopBoxNumericKeypad device, string channel); - - /// - /// Delegate for PresetsSaved event, which is fired when presets are saved. Provides the list of presets that were saved. - /// - public delegate void PresetsSavedCallback(List presets); - - private readonly object _fileOps = new(); - private readonly bool _initSuccess; - - private readonly ISetTopBoxNumericKeypad _setTopBox; - - /// - /// The methods on the STB device to call when dialing - /// - private Dictionary> _dialFunctions; - - private bool _dialIsRunning; - private Action _enterFunction; - private string _filePath; - - /// - /// Constructor for DevicePresetsModel when a set top box device is included. If the set top box does not implement the required INumericKeypad interface, the model will still be created but dialing functionality will be disabled and a message will be logged. - /// - /// - /// - /// - public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName) - : this(key, fileName) + public class DevicePresetsModel : Device { - try - { - _setTopBox = setTopBox; + /// + /// Delegate for preset recalled event + /// + /// device that recalled a preset + /// channel that was recalled + public delegate void PresetRecalledCallback(ISetTopBoxNumericKeypad device, string channel); - // Grab the digit functions from the device - // If any fail, the whole thing fails peacefully + /// + /// Delegate for presets saved event + /// + /// list of presets that were saved + public delegate void PresetsSavedCallback(List presets); + + private readonly CCriticalSection _fileOps = new CCriticalSection(); + private readonly bool _initSuccess; + + private readonly ISetTopBoxNumericKeypad _setTopBox; + + /// + /// The methods on the STB device to call when dialing + /// + private Dictionary> _dialFunctions; + + private bool _dialIsRunning; + private Action _enterFunction; + private string _filePath; + + /// + /// Constructor + /// + /// key for the device + /// set top box device + /// file name for presets + public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName) + : this(key, fileName) + { + try + { + _setTopBox = setTopBox; + + // Grab the digit functions from the device + // If any fail, the whole thing fails peacefully + _dialFunctions = new Dictionary>(10) + { + {'1', setTopBox.Digit1}, + {'2', setTopBox.Digit2}, + {'3', setTopBox.Digit3}, + {'4', setTopBox.Digit4}, + {'5', setTopBox.Digit5}, + {'6', setTopBox.Digit6}, + {'7', setTopBox.Digit7}, + {'8', setTopBox.Digit8}, + {'9', setTopBox.Digit9}, + {'0', setTopBox.Digit0}, + {'-', setTopBox.Dash} + }; + } + catch + { + Debug.LogMessage(LogEventLevel.Information, "DevicePresets '{0}', not attached to INumericKeypad device. Ignoring", key); + _dialFunctions = null; + return; + } + + _enterFunction = setTopBox.KeypadEnter; + } + + /// + /// Constructor + /// + /// key for the device + /// file name for presets + public DevicePresetsModel(string key, string fileName) : base(key) + { + PulseTime = 150; + DigitSpacingMs = 150; + + UseLocalImageStorage = true; + + ImagesLocalHostPrefix = "http://" + CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + ImagesPathPrefix = @"/presets/images.zip/"; + ListPathPrefix = @"/html/presets/lists/"; + + SetFileName(fileName); + + _initSuccess = true; + } + + /// + /// Event fired when a preset is recalled + /// + public event PresetRecalledCallback PresetRecalled; + + /// + /// Event fired when presets are saved + /// + public event PresetsSavedCallback PresetsSaved; + + /// + /// Gets or sets the PulseTime + /// + public int PulseTime { get; set; } + + /// + /// Gets or sets the DigitSpacingMs + /// + public int DigitSpacingMs { get; set; } + + /// + /// Gets or sets the PresetsAreLoaded + /// + public bool PresetsAreLoaded { get; private set; } + + /// + /// Gets or sets the PresetsList + /// + public List PresetsList { get; private set; } + + /// + /// Gets the Count of presets + /// + public int Count + { + get { return PresetsList != null ? PresetsList.Count : 0; } + } + + /// + /// Gets or sets the UseLocalImageStorage + /// + public bool UseLocalImageStorage { get; set; } + + /// + /// Gets or sets the ImagesLocalHostPrefix + /// + public string ImagesLocalHostPrefix { get; set; } + + /// + /// Gets or sets the ImagesPathPrefix + /// + public string ImagesPathPrefix { get; set; } + + /// + /// Gets or sets the ListPathPrefix + /// + public string ListPathPrefix { get; set; } + + /// + /// Event fired when presets are loaded + /// + public event EventHandler PresetsLoaded; + + + /// + /// SetFileName method + /// + public void SetFileName(string path) + { + _filePath = ListPathPrefix + path; + + Debug.LogMessage(LogEventLevel.Verbose, this, "Setting presets file path to {0}", _filePath); + LoadChannels(); + } + + /// + /// LoadChannels method + /// + public void LoadChannels() + { + try + { + _fileOps.Enter(); + + Debug.LogMessage(LogEventLevel.Verbose, this, "Loading presets from {0}", _filePath); + PresetsAreLoaded = false; + try + { + var pl = JsonConvert.DeserializeObject(File.ReadToEnd(_filePath, Encoding.ASCII)); + Name = pl.Name; + PresetsList = pl.Channels; + + Debug.LogMessage(LogEventLevel.Verbose, this, "Loaded {0} presets", PresetsList.Count); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, + "LoadChannels: Error reading presets file. These presets will be empty:\r '{0}'\r Error:{1}", + _filePath, e.Message); + // Just save a default empty list + PresetsList = new List(); + } + PresetsAreLoaded = true; + + var handler = PresetsLoaded; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + finally + { + _fileOps.Leave(); + } + } + + /// + /// Dial method + /// + public void Dial(int presetNum) + { + if (presetNum <= PresetsList.Count) + { + Dial(PresetsList[presetNum - 1].Channel); + } + } + + /// + /// Dial method + /// + public void Dial(string chanNum) + { + if (_dialIsRunning || !_initSuccess) + { + return; + } + if (_dialFunctions == null) + { + Debug.LogMessage(LogEventLevel.Debug, "DevicePresets '{0}', not attached to keypad device. Ignoring channel", Key); + return; + } + + _dialIsRunning = true; + CrestronInvoke.BeginInvoke(o => + { + foreach (var c in chanNum.ToCharArray()) + { + if (_dialFunctions.ContainsKey(c)) + { + Pulse(_dialFunctions[c]); + } + CrestronEnvironment.Sleep(DigitSpacingMs); + } + + if (_enterFunction != null) + { + Pulse(_enterFunction); + } + _dialIsRunning = false; + }); + + if (_setTopBox == null) return; + + OnPresetRecalled(_setTopBox, chanNum); + } + + /// + /// Dial method + /// + public void Dial(int presetNum, ISetTopBoxNumericKeypad setTopBox) + { + if (presetNum <= PresetsList.Count) + { + Dial(PresetsList[presetNum - 1].Channel, setTopBox); + } + } + + /// + /// Dial method + /// + public void Dial(string chanNum, ISetTopBoxNumericKeypad setTopBox) + { _dialFunctions = new Dictionary>(10) { {'1', setTopBox.Digit1}, @@ -73,316 +304,89 @@ public class DevicePresetsModel : Device {'0', setTopBox.Digit0}, {'-', setTopBox.Dash} }; - } - catch - { - Debug.LogMessage(LogEventLevel.Information, "DevicePresets '{0}', not attached to INumericKeypad device. Ignoring", key); - _dialFunctions = null; - return; + + _enterFunction = setTopBox.KeypadEnter; + + OnPresetRecalled(setTopBox, chanNum); + + Dial(chanNum); } - _enterFunction = setTopBox.KeypadEnter; - } - - /// - /// Constructor for DevicePresetsModel when only a file name is provided. Dialing functionality will be disabled. - /// - /// - /// - public DevicePresetsModel(string key, string fileName) : base(key) - { - PulseTime = 150; - DigitSpacingMs = 150; - - UseLocalImageStorage = true; - - ImagesLocalHostPrefix = "http://" + CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - ImagesPathPrefix = @"/presets/images.zip/"; - ListPathPrefix = @"/html/presets/lists/"; - - SetFileName(fileName); - - _initSuccess = true; - } - - /// - /// Event fired when a preset is recalled, providing the device and channel that was recalled - /// - public event PresetRecalledCallback PresetRecalled; - - /// - /// Event fired when presets are saved, providing the list of presets that were saved - /// - public event PresetsSavedCallback PresetsSaved; - - /// - /// Time in milliseconds to pulse the digit for when dialing a channel - /// - public int PulseTime { get; private set; } - - /// - /// Time in milliseconds to wait between pulsing digits when dialing a channel - /// - public int DigitSpacingMs { get; private set; } - - /// - /// Whether the presets have finished loading from the file or not - /// - public bool PresetsAreLoaded { get; private set; } - - /// - /// The list of presets to display - /// - public List PresetsList { get; private set; } - - /// - /// The number of presets in the list - /// - public int Count - { - get { return PresetsList != null ? PresetsList.Count : 0; } - } - - /// - /// Indicates whether to use local image storage for preset images, which allows for more and larger images than the SIMPL+ zip file method - /// - public bool UseLocalImageStorage { get; private set; } - - /// - /// The prefix for the local host URL for preset images - /// - public string ImagesLocalHostPrefix { get; private set; } - - /// - /// The path prefix for preset images - /// - public string ImagesPathPrefix { get; private set; } - - /// - /// The path prefix for preset lists - /// - public string ListPathPrefix { get; private set; } - - /// - /// Event fired when presets are loaded - /// - public event EventHandler PresetsLoaded; - - /// - /// Sets the file name for the presets list and loads the presets from that file. The file should be a JSON file in the format of the PresetsList class. If the file cannot be read, an empty list will be created and a message will be logged. This method is thread safe. - /// - /// The path to the presets file. - public void SetFileName(string path) - { - _filePath = ListPathPrefix + path; - - Debug.LogMessage(LogEventLevel.Verbose, this, "Setting presets file path to {0}", _filePath); - LoadChannels(); - } - - /// - /// Loads the presets from the file specified by _filePath. - /// - public void LoadChannels() - { - lock (_fileOps) + private void OnPresetRecalled(ISetTopBoxNumericKeypad setTopBox, string channel) + { + var handler = PresetRecalled; + + if (handler == null) + { + return; + } + + handler(setTopBox, channel); + } + + /// + /// UpdatePreset method + /// + public void UpdatePreset(int index, PresetChannel preset) + { + if (index >= PresetsList.Count) + { + return; + } + + PresetsList[index] = preset; + + SavePresets(); + + OnPresetsSaved(); + } + + /// + /// UpdatePresets method + /// + public void UpdatePresets(List presets) + { + PresetsList = presets; + + SavePresets(); + + OnPresetsSaved(); + } + + private void SavePresets() { - Debug.LogMessage(LogEventLevel.Verbose, this, "Loading presets from {0}", _filePath); - PresetsAreLoaded = false; try { - var pl = JsonConvert.DeserializeObject(File.ReadToEnd(_filePath, Encoding.ASCII)); - Name = pl.Name; - PresetsList = pl.Channels; + _fileOps.Enter(); + var pl = new PresetsList {Channels = PresetsList, Name = Name}; + var json = JsonConvert.SerializeObject(pl, Formatting.Indented); - Debug.LogMessage(LogEventLevel.Verbose, this, "Loaded {0} presets", PresetsList.Count); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, this, - "LoadChannels: Error reading presets file. These presets will be empty:\r '{0}'\r Error:{1}", - _filePath, e.Message); - // Just save a default empty list - PresetsList = new List(); - } - PresetsAreLoaded = true; - - var handler = PresetsLoaded; - if (handler != null) - { - handler(this, EventArgs.Empty); - } - } - } - - /// - /// Dials a preset by its number in the list (starting at 1). If the preset number is out of range, nothing will happen. - /// - /// The number of the preset to dial, starting at 1 - public void Dial(int presetNum) - { - if (presetNum <= PresetsList.Count) - { - Dial(PresetsList[presetNum - 1].Channel); - } - } - - /// - /// Dials a preset by its channel number. If the channel number contains characters that are not 0-9 or '-', those characters will be ignored. - /// If the model was not initialized with a valid set top box device, dialing will be disabled and a message will be logged. - /// The channel number to dial - public void Dial(string chanNum) - { - if (_dialIsRunning || !_initSuccess) - { - return; - } - if (_dialFunctions == null) - { - Debug.LogMessage(LogEventLevel.Debug, "DevicePresets '{0}', not attached to keypad device. Ignoring channel", Key); - return; - } - - _dialIsRunning = true; - Task.Run(() => - { - foreach (var c in chanNum.ToCharArray()) - { - if (_dialFunctions.ContainsKey(c)) + using (var file = File.Open(_filePath, FileMode.Truncate)) { - Pulse(_dialFunctions[c]); + file.Write(json, Encoding.UTF8); } - CrestronEnvironment.Sleep(DigitSpacingMs); } - - if (_enterFunction != null) + finally { - Pulse(_enterFunction); + _fileOps.Leave(); } - _dialIsRunning = false; - }); - - if (_setTopBox == null) return; - - OnPresetRecalled(_setTopBox, chanNum); - } - - /// - /// Dials a preset by its number in the list (starting at 1) using the provided set top box device. If the preset number is out of range, nothing will happen. - /// - /// - /// - public void Dial(int presetNum, ISetTopBoxNumericKeypad setTopBox) - { - if (presetNum <= PresetsList.Count) - { - Dial(PresetsList[presetNum - 1].Channel, setTopBox); - } - } - - /// - /// Dials a preset by its channel number using the provided set top box device. If the channel number contains characters that are not 0-9 or '-', those characters will be ignored. - /// If the provided set top box device does not implement the required INumericKeypad interface, dialing will be disabled and a message will be logged. - /// If the model was not initialized with a valid set top box device, dialing will be disabled and a message will be logged. - /// - /// - /// - public void Dial(string chanNum, ISetTopBoxNumericKeypad setTopBox) - { - _dialFunctions = new Dictionary>(10) - { - {'1', setTopBox.Digit1}, - {'2', setTopBox.Digit2}, - {'3', setTopBox.Digit3}, - {'4', setTopBox.Digit4}, - {'5', setTopBox.Digit5}, - {'6', setTopBox.Digit6}, - {'7', setTopBox.Digit7}, - {'8', setTopBox.Digit8}, - {'9', setTopBox.Digit9}, - {'0', setTopBox.Digit0}, - {'-', setTopBox.Dash} - }; - - _enterFunction = setTopBox.KeypadEnter; - - OnPresetRecalled(setTopBox, chanNum); - - Dial(chanNum); - } - - private void OnPresetRecalled(ISetTopBoxNumericKeypad setTopBox, string channel) - { - var handler = PresetRecalled; - - if (handler == null) - { - return; + } - handler(setTopBox, channel); - } - - /// - /// Updates the preset at the given index with the provided preset information, then saves the updated presets list to the file. If the index is out of range, nothing will happen. - /// - /// The index of the preset to update, starting at 0 - /// The preset information to update - public void UpdatePreset(int index, PresetChannel preset) - { - if (index >= PresetsList.Count) + private void OnPresetsSaved() { - return; + var handler = PresetsSaved; + + if (handler == null) return; + + handler(PresetsList); } - PresetsList[index] = preset; - - SavePresets(); - - OnPresetsSaved(); - } - - /// - /// Updates the entire presets list with the provided list, then saves the updated presets list to the file. If the provided list is null, nothing will happen. - /// - /// - public void UpdatePresets(List presets) - { - PresetsList = presets; - - SavePresets(); - - OnPresetsSaved(); - } - - private void SavePresets() - { - lock (_fileOps) + private void Pulse(Action act) { - var pl = new PresetsList { Channels = PresetsList, Name = Name }; - var json = JsonConvert.SerializeObject(pl, Formatting.Indented); - - using (var file = File.Open(_filePath, FileMode.Truncate)) - { - file.Write(json, Encoding.UTF8); - } + act(true); + CrestronEnvironment.Sleep(PulseTime); + act(false); } } - - private void OnPresetsSaved() - { - var handler = PresetsSaved; - - if (handler == null) return; - - handler(PresetsList); - } - - private void Pulse(Action act) - { - act(true); - CrestronEnvironment.Sleep(PulseTime); - act(false); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Presets/DevicePresetsView.cs b/src/PepperDash.Essentials.Core/Presets/DevicePresetsView.cs index 57be7624..42761de7 100644 --- a/src/PepperDash.Essentials.Core/Presets/DevicePresetsView.cs +++ b/src/PepperDash.Essentials.Core/Presets/DevicePresetsView.cs @@ -6,87 +6,88 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core.Presets; - -/// -/// Represents a DevicePresetsView -/// -public class DevicePresetsView +namespace PepperDash.Essentials.Core.Presets { /// - /// Gets or sets the ShowNumbers + /// Represents a DevicePresetsView /// - public bool ShowNumbers { get; set; } - - /// - /// Gets or sets the ShowName - /// - public bool ShowName { get; set; } - - /// - /// Gets or sets the ShowIcon - /// - public bool ShowIcon { get; set; } - - /// - /// Gets the SubpageReferenceList - /// - public SubpageReferenceList SRL { get; private set; } - - /// - /// Gets the DevicePresetsModel - /// - public DevicePresetsModel Model { get; private set; } - - /// - /// Constructor - /// - /// trilst - /// device presets model - /// - public DevicePresetsView(BasicTriListWithSmartObject tl, DevicePresetsModel model) + public class DevicePresetsView { - if (model == null) + /// + /// Gets or sets the ShowNumbers + /// + public bool ShowNumbers { get; set; } + + /// + /// Gets or sets the ShowName + /// + public bool ShowName { get; set; } + + /// + /// Gets or sets the ShowIcon + /// + public bool ShowIcon { get; set; } + + /// + /// Gets or sets the SRL + /// + public SubpageReferenceList SRL { get; private set; } + + /// + /// Gets or sets the Model + /// + public DevicePresetsModel Model { get; private set; } + + /// + /// Constructor + /// + /// trilst + /// device presets model + /// + public DevicePresetsView(BasicTriListWithSmartObject tl, DevicePresetsModel model) { - throw new ArgumentNullException("model", "DevicePresetsView Cannot be instantiated with null model"); - } - ShowIcon = true; - ShowName = true; - - Model = model; - - SRL = new SubpageReferenceList(tl, 10012, 3, 0, 4); - Model.PresetsLoaded += new EventHandler(Model_PresetsLoaded); - } - - /// - /// Attach method - /// - public void Attach() - { - if (Model.PresetsAreLoaded) - { - uint index = 1; - foreach (var p in Model.PresetsList) + if (model == null) { - SRL.AddItem(new PresetsListSubpageReferenceListItem(p, index, SRL, this)); - index++; + throw new ArgumentNullException("model", "DevicePresetsView Cannot be instantiated with null model"); } - SRL.Count = (ushort)Model.PresetsList.Count; + ShowIcon = true; + ShowName = true; + + Model = model; + + SRL = new SubpageReferenceList(tl, 10012, 3, 0, 4); + Model.PresetsLoaded += new EventHandler(Model_PresetsLoaded); } - } - /// - /// Detach method - /// - public void Detach() - { - SRL.Clear(); - } + /// + /// Attach method + /// + public void Attach() + { + if (Model.PresetsAreLoaded) + { + uint index = 1; + foreach (var p in Model.PresetsList) + { + SRL.AddItem(new PresetsListSubpageReferenceListItem(p, index, SRL, this)); + index++; + } + SRL.Count = (ushort)Model.PresetsList.Count; + } + } - void Model_PresetsLoaded(object sender, EventArgs e) - { - Detach(); - Attach(); + /// + /// Detach method + /// + public void Detach() + { + SRL.Clear(); + } + + void Model_PresetsLoaded(object sender, EventArgs e) + { + Detach(); + Attach(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Presets/PresetChannel.cs b/src/PepperDash.Essentials.Core/Presets/PresetChannel.cs index d7a9ffa9..8fd15105 100644 --- a/src/PepperDash.Essentials.Core/Presets/PresetChannel.cs +++ b/src/PepperDash.Essentials.Core/Presets/PresetChannel.cs @@ -7,47 +7,48 @@ using System.Text; using Crestron.SimplSharp; using Newtonsoft.Json; -namespace PepperDash.Essentials.Core.Presets; - -/// -/// Represents a PresetChannel -/// -public class PresetChannel +namespace PepperDash.Essentials.Core.Presets { + /// + /// Represents a PresetChannel + /// + public class PresetChannel + { + + /// + /// Gets or sets the Name + /// + [JsonProperty(Required = Required.Always,PropertyName = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the IconUrl + /// + [JsonProperty(Required = Required.Always, PropertyName = "iconUrl")] + public string IconUrl { get; set; } + + /// + /// Gets or sets the Channel + /// + [JsonProperty(Required = Required.Always, PropertyName = "channel")] + public string Channel { get; set; } + } /// - /// Gets or sets the Name + /// Represents a PresetsList /// - [JsonProperty(Required = Required.Always, PropertyName = "name")] - public string Name { get; set; } + public class PresetsList + { + /// + /// Gets or sets the Name + /// + [JsonProperty(Required=Required.Always,PropertyName = "name")] + public string Name { get; set; } - /// - /// Gets or sets the IconUrl - /// - [JsonProperty(Required = Required.Always, PropertyName = "iconUrl")] - public string IconUrl { get; set; } - - /// - /// Gets or sets the Channel - /// - [JsonProperty(Required = Required.Always, PropertyName = "channel")] - public string Channel { get; set; } -} - -/// -/// Represents a PresetsList -/// -public class PresetsList -{ - /// - /// Gets or sets the Name - /// - [JsonProperty(Required = Required.Always, PropertyName = "name")] - public string Name { get; set; } - - /// - /// Gets or sets the Channels - /// - [JsonProperty(Required = Required.Always, PropertyName = "channels")] - public List Channels { get; set; } + /// + /// Gets or sets the Channels + /// + [JsonProperty(Required = Required.Always, PropertyName = "channels")] + public List Channels { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Presets/PresetsListSubpageReferenceListItem.cs b/src/PepperDash.Essentials.Core/Presets/PresetsListSubpageReferenceListItem.cs index 3453c3ad..f7bfa3bd 100644 --- a/src/PepperDash.Essentials.Core/Presets/PresetsListSubpageReferenceListItem.cs +++ b/src/PepperDash.Essentials.Core/Presets/PresetsListSubpageReferenceListItem.cs @@ -10,8 +10,11 @@ using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Presets; - +namespace PepperDash.Essentials.Core.Presets +{ + /// + /// Represents a PresetsListSubpageReferenceListItem + /// public class PresetsListSubpageReferenceListItem : SubpageReferenceListItem { DevicePresetsView View; @@ -61,4 +64,5 @@ namespace PepperDash.Essentials.Core.Presets; var icon = View.ShowIcon ? url : ""; Owner.StringInputSig(Index, 3).StringValue = icon; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Queues/ComsMessage.cs b/src/PepperDash.Essentials.Core/Queues/ComsMessage.cs index 5d17b7fe..1eb3a281 100644 --- a/src/PepperDash.Essentials.Core/Queues/ComsMessage.cs +++ b/src/PepperDash.Essentials.Core/Queues/ComsMessage.cs @@ -1,72 +1,74 @@ using System; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Queues; - -/// -/// IBasicCommunication Message for IQueue -/// -public class ComsMessage : IQueueMessage +namespace PepperDash.Essentials.Core.Queues { - private readonly byte[] _bytes; - private readonly IBasicCommunication _coms; - private readonly string _string; - private readonly bool _isByteMessage; - /// /// Represents a ComsMessage /// - /// IBasicCommunication to send the message - /// Message to send - public ComsMessage(IBasicCommunication coms, string message) + public class ComsMessage : IQueueMessage { - Validate(coms, message); - _coms = coms; - _string = message; - } + private readonly byte[] _bytes; + private readonly IBasicCommunication _coms; + private readonly string _string; + private readonly bool _isByteMessage; - /// - /// Constructor for a byte message - /// - /// IBasicCommunication to send the message - /// Message to send - public ComsMessage(IBasicCommunication coms, byte[] message) - { - Validate(coms, message); - _coms = coms; - _bytes = message; - _isByteMessage = true; - } - - private void Validate(IBasicCommunication coms, object message) - { - if (coms == null) - throw new ArgumentNullException("coms"); - - if (message == null) - throw new ArgumentNullException("message"); - } - - /// - /// Dispatchs the string/byte[] to the IBasicCommunication specified - /// - public void Dispatch() - { - if (_isByteMessage) + /// + /// Constructor for a string message + /// + /// IBasicCommunication to send the message + /// Message to send + public ComsMessage(IBasicCommunication coms, string message) { - _coms.SendBytes(_bytes); + Validate(coms, message); + _coms = coms; + _string = message; } - else - { - _coms.SendText(_string); - } - } - /// - /// Shows either the byte[] or string to be sent - /// - public override string ToString() - { - return _bytes != null ? _bytes.ToString() : _string; + /// + /// Constructor for a byte message + /// + /// IBasicCommunication to send the message + /// Message to send + public ComsMessage(IBasicCommunication coms, byte[] message) + { + Validate(coms, message); + _coms = coms; + _bytes = message; + _isByteMessage = true; + } + + private void Validate(IBasicCommunication coms, object message) + { + if (coms == null) + throw new ArgumentNullException("coms"); + + if (message == null) + throw new ArgumentNullException("message"); + } + + /// + /// Dispatch method + /// + public void Dispatch() + { + if (_isByteMessage) + { + _coms.SendBytes(_bytes); + } + else + { + _coms.SendText(_string); + } + } + + /// + /// ToString method + /// + /// + public override string ToString() + { + return _bytes != null ? _bytes.ToString() : _string; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Queues/GenericQueue.cs b/src/PepperDash.Essentials.Core/Queues/GenericQueue.cs index 09d07a05..af76f10f 100644 --- a/src/PepperDash.Essentials.Core/Queues/GenericQueue.cs +++ b/src/PepperDash.Essentials.Core/Queues/GenericQueue.cs @@ -4,282 +4,284 @@ using System.Threading; using Crestron.SimplSharp; using PepperDash.Core; using Serilog.Events; +using Thread = Crestron.SimplSharpPro.CrestronThread.Thread; -namespace PepperDash.Essentials.Core.Queues; - - -// TODO: The capacity argument in the constructors should be removed. Now that this class uses System.Threading rather than the Crestron library, there is no longer a thread capacity limit. -// If a capacity limit is needed, it should be implemented by the caller by checking the QueueCount property before enqueuing items and deciding how to handle the situation when the queue is too full (e.g. drop messages, log warnings, etc.) - -/// -/// Threadsafe processing of queued items with pacing if required -/// -public class GenericQueue : IQueue +namespace PepperDash.Essentials.Core.Queues { - private readonly string _key; - /// - /// Returns the number of items currently in the queue. This is not threadsafe, so it should only be used for informational purposes and not for processing logic. + /// Threadsafe processing of queued items with pacing if required /// - protected readonly ConcurrentQueue _queue; - - /// - /// The thread that processes the queue items - /// - protected readonly Thread _worker; - private readonly object _lock = new(); - - private bool _delayEnabled; - private int _delayTime; - - private const ThreadPriority _defaultPriority = ThreadPriority.Normal; - - /// - /// If the instance has been disposed. - /// - public bool Disposed { get; private set; } - - /// - /// Returns the capacity of the CrestronQueue (fixed Size property) - /// - public int QueueCapacity + public class GenericQueue : IQueue { - get + private readonly string _key; + + /// + /// The internal queue + /// + protected readonly ConcurrentQueue _queue; + + /// + /// The worker thread + /// + protected readonly Thread _worker; + + /// + /// The wait handle for the queue + /// + protected readonly CEvent _waitHandle = new CEvent(); + + private bool _delayEnabled; + private int _delayTime; + + private const Thread.eThreadPriority _defaultPriority = Thread.eThreadPriority.MediumPriority; + + /// + /// Gets or sets the Disposed + /// + public bool Disposed { get; private set; } + + /// + /// Returns the capacity of the CrestronQueue (fixed Size property) + /// + public int QueueCapacity { - return int.MaxValue; - } - } - - /// - /// Returns the number of elements currently in the CrestronQueue - /// - public int QueueCount - { - get - { - return _queue.Count; - } - } - - /// - /// Constructor with no thread priority - /// - /// - public GenericQueue(string key) - : this(key, _defaultPriority, 0, 0) - { - } - - /// - /// Constructor with queue size - /// - /// - /// Fixed size for the queue to hold - public GenericQueue(string key, int capacity) - : this(key, _defaultPriority, capacity, 0) - { - } - - /// - /// Constructor for generic queue with no pacing - /// - /// Key - /// Pacing in ms between actions - public GenericQueue(int pacing, string key) - : this(key, _defaultPriority, 0, pacing) - { - } - - /// - /// Constructor with pacing and capacity - /// - /// - /// - /// - public GenericQueue(string key, int pacing, int capacity) - : this(key, _defaultPriority, capacity, pacing) - { - } - - /// - /// Constructor with pacing and priority - /// - /// - /// - /// - public GenericQueue(string key, int pacing, ThreadPriority priority) - : this(key, priority, 0, pacing) - { - } - - /// - /// Constructor with pacing, priority and capacity - /// - /// - /// - /// - public GenericQueue(string key, ThreadPriority priority, int capacity) - : this(key, priority, capacity, 0) - { - } - - /// - /// Constructor with pacing, priority and capacity - /// - /// - /// - /// - /// - public GenericQueue(string key, int pacing, ThreadPriority priority, int capacity) - : this(key, priority, capacity, pacing) - { - } - - /// - /// Constructor for generic queue with no pacing - /// - /// Key - /// - /// - /// - protected GenericQueue(string key, ThreadPriority priority, int capacity, int pacing) - { - _key = key; - - _queue = new ConcurrentQueue(); - _worker = new Thread(ProcessQueue) - { - Priority = priority, - Name = _key, - IsBackground = true - }; - _worker.Start(); - - SetDelayValues(pacing); - } - - private void SetDelayValues(int pacing) - { - _delayEnabled = pacing > 0; - _delayTime = pacing; - - CrestronEnvironment.ProgramStatusEventHandler += programEvent => - { - if (programEvent != eProgramStatusEventType.Stopping) - return; - - Dispose(true); - }; - } - - /// - /// Thread callback - /// - /// Null when the thread is exited - private void ProcessQueue() - { - while (true) - { - if (_queue.TryDequeue(out var item) && item == null) - break; - - if (item != null) + get { - try - { - //Debug.LogMessage(LogEventLevel.Verbose, this, "Processing queue item: '{0}'", item.ToString()); - item.Dispatch(); + return int.MaxValue; + } + } - if (_delayEnabled) - Thread.Sleep(_delayTime); - } - catch (ThreadInterruptedException) - { - //swallowing this exception, as it should only happen on shut down - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Information, this, "Caught an exception in the Queue: {1}:{0}", ex.Message, ex); - Debug.LogMessage(LogEventLevel.Verbose, this, "Stack Trace: {0}", ex.StackTrace); + /// + /// Returns the number of elements currently in the CrestronQueue + /// + public int QueueCount + { + get + { + return _queue.Count; + } + } - if (ex.InnerException != null) + /// + /// Constructor with no thread priority + /// + /// + public GenericQueue(string key) + : this(key, _defaultPriority, 0, 0) + { + } + + /// + /// Constructor with queue size + /// + /// + /// Fixed size for the queue to hold + public GenericQueue(string key, int capacity) + : this(key, _defaultPriority, capacity, 0) + { + } + + /// + /// Constructor for generic queue with no pacing + /// + /// Key + /// Pacing in ms between actions + public GenericQueue(int pacing, string key) + : this(key, _defaultPriority, 0, pacing) + { + } + + /// + /// Constructor with pacing and capacity + /// + /// + /// + /// + public GenericQueue(string key, int pacing, int capacity) + : this(key, _defaultPriority, capacity, pacing) + { + } + + /// + /// Constructor with pacing and priority + /// + /// + /// + /// + public GenericQueue(string key, int pacing, Thread.eThreadPriority priority) + : this(key, priority, 0, pacing) + { + } + + /// + /// Constructor with pacing, priority and capacity + /// + /// + /// + /// + public GenericQueue(string key, Thread.eThreadPriority priority, int capacity) + : this(key, priority, capacity, 0) + { + } + + /// + /// Constructor with pacing, priority and capacity + /// + /// + /// + /// + /// + public GenericQueue(string key, int pacing, Thread.eThreadPriority priority, int capacity) + : this(key, priority, capacity, pacing) + { + } + + /// + /// Constructor for generic queue with no pacing + /// + /// Key + /// + /// + /// + protected GenericQueue(string key, Thread.eThreadPriority priority, int capacity, int pacing) + { + _key = key; + int cap = 25; // sets default + if (capacity > 0) + { + cap = capacity; // overrides default + } + + _queue = new ConcurrentQueue(); + _worker = new Thread(ProcessQueue, null, Thread.eThreadStartOptions.Running) + { + Priority = priority, + Name = _key + }; + + SetDelayValues(pacing); + } + + private void SetDelayValues(int pacing) + { + _delayEnabled = pacing > 0; + _delayTime = pacing; + + CrestronEnvironment.ProgramStatusEventHandler += programEvent => + { + if (programEvent != eProgramStatusEventType.Stopping) + return; + + Dispose(true); + }; + } + + /// + /// Thread callback + /// + /// The action used to process dequeued items + /// Null when the thread is exited + private object ProcessQueue(object obj) + { + while (true) + { + if (_queue.TryDequeue(out var item) && item == null) + break; + + if (item != null) + { + try { - Debug.LogMessage(LogEventLevel.Information, this, "---\r\n{0}", ex.InnerException.Message); - Debug.LogMessage(LogEventLevel.Verbose, this, "Stack Trace: {0}", ex.InnerException.StackTrace); + //Debug.LogMessage(LogEventLevel.Verbose, this, "Processing queue item: '{0}'", item.ToString()); + item.Dispatch(); + + if (_delayEnabled) + Thread.Sleep(_delayTime); + } + catch (ThreadAbortException) + { + //swallowing this exception, as it should only happen on shut down + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Information, this, "Caught an exception in the Queue: {1}:{0}", ex.Message, ex); + Debug.LogMessage(LogEventLevel.Verbose, this, "Stack Trace: {0}", ex.StackTrace); + + if (ex.InnerException != null) + { + Debug.LogMessage(LogEventLevel.Information, this, "---\r\n{0}", ex.InnerException.Message); + Debug.LogMessage(LogEventLevel.Verbose, this, "Stack Trace: {0}", ex.InnerException.StackTrace); + } } } + else _waitHandle.Wait(); } - else + + return null; + } + + /// + /// Enqueue method + /// + public void Enqueue(IQueueMessage item) + { + if (Disposed) { - lock (_lock) + Debug.LogMessage(LogEventLevel.Debug, this, "Queue has been disposed. Enqueuing messages not allowed while program is stopping."); + return; + } + + _queue.Enqueue(item); + _waitHandle.Set(); + } + + /// + /// Dispose method + /// + public void Dispose() + { + Dispose(true); + CrestronEnvironment.GC.SuppressFinalize(this); + } + + /// + /// Actually does the disposing. If you override this method, be sure to either call the base implementation + /// or clean up all the resources yourself. + /// + /// set to true unless called from finalizer + protected void Dispose(bool disposing) + { + if (Disposed) + return; + + if (disposing) + { + using (_waitHandle) { - if (_queue.IsEmpty) - Monitor.Wait(_lock); + Debug.LogMessage(LogEventLevel.Verbose, this, "Disposing..."); + _queue.Enqueue(null); + _waitHandle.Set(); + _worker.Join(); } } - } - } - /// - /// Enqueues an item to be processed by the queue thread. If the queue has been disposed, the item will not be enqueued and a message will be logged. - /// - /// - public void Enqueue(IQueueMessage item) - { - if (Disposed) + Disposed = true; + } + + /// + /// Finalizer + /// + ~GenericQueue() { - Debug.LogMessage(LogEventLevel.Debug, this, "Queue has been disposed. Enqueuing messages not allowed while program is stopping."); - return; + Dispose(true); } - _queue.Enqueue(item); - lock (_lock) - Monitor.Pulse(_lock); - } - - /// - /// Disposes the thread and cleans up resources. Thread cannot be restarted once - /// disposed. - /// - public void Dispose() - { - Dispose(true); - CrestronEnvironment.GC.SuppressFinalize(this); - } - - /// - /// Actually does the disposing. If you override this method, be sure to either call the base implementation - /// or clean up all the resources yourself. - /// - /// set to true unless called from finalizer - protected void Dispose(bool disposing) - { - if (Disposed) - return; - - if (disposing) + /// + /// Key + /// + public string Key { - Debug.LogMessage(LogEventLevel.Verbose, this, "Disposing..."); - _queue.Enqueue(null); - lock (_lock) - Monitor.Pulse(_lock); - _worker.Join(); + get { return _key; } } - - Disposed = true; - } - - /// Finalizer in case Dispose is not called. This will clean up the thread, but any items still in the queue will not be processed and could potentially be lost. - ~GenericQueue() - { - Dispose(true); - } - - /// - /// Key - /// - public string Key - { - get { return _key; } } } diff --git a/src/PepperDash.Essentials.Core/Queues/IQueue.cs b/src/PepperDash.Essentials.Core/Queues/IQueue.cs index 2eb6a5d1..a8cbbf6a 100644 --- a/src/PepperDash.Essentials.Core/Queues/IQueue.cs +++ b/src/PepperDash.Essentials.Core/Queues/IQueue.cs @@ -1,23 +1,22 @@ using System; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Queues; - - -/// -/// Defines the contract for IQueue -/// -public interface IQueue : IKeyed, IDisposable where T : class +namespace PepperDash.Essentials.Core.Queues { /// - /// Enqueues an item + /// Defines the contract for IQueue /// - /// item to be queued - void Enqueue(T item); + public interface IQueue : IKeyed, IDisposable where T : class + { + /// + /// Enqueues an item + /// + /// item to be queued + void Enqueue(T item); - /// - /// gets the disposed status of the queue - /// - bool Disposed { get; } + /// + /// gets the disposed status of the queue + /// + bool Disposed { get; } + } } - diff --git a/src/PepperDash.Essentials.Core/Queues/IQueueMessage.cs b/src/PepperDash.Essentials.Core/Queues/IQueueMessage.cs index a832cd46..2a050a3b 100644 --- a/src/PepperDash.Essentials.Core/Queues/IQueueMessage.cs +++ b/src/PepperDash.Essentials.Core/Queues/IQueueMessage.cs @@ -1,15 +1,15 @@ using System; -namespace PepperDash.Essentials.Core.Queues; - - -/// -/// Defines the contract for IQueueMessage -/// -public interface IQueueMessage +namespace PepperDash.Essentials.Core.Queues { /// - /// Dispatches the message + /// Defines the contract for IQueueMessage /// - void Dispatch(); -} + public interface IQueueMessage + { + /// + /// Dispatches the message + /// + void Dispatch(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Queues/ProcessStringMessage.cs b/src/PepperDash.Essentials.Core/Queues/ProcessStringMessage.cs index 34fa8617..5e2db7df 100644 --- a/src/PepperDash.Essentials.Core/Queues/ProcessStringMessage.cs +++ b/src/PepperDash.Essentials.Core/Queues/ProcessStringMessage.cs @@ -1,48 +1,48 @@ using System; -namespace PepperDash.Essentials.Core.Queues; - -/// -/// Message class for processing strings via an IQueue -/// -public class ProcessStringMessage : IQueueMessage +namespace PepperDash.Essentials.Core.Queues { - private readonly Action _action; - private readonly string _message; - /// /// Represents a ProcessStringMessage /// - /// Message to be processed - /// Action to invoke on the message - public ProcessStringMessage(string message, Action action) + public class ProcessStringMessage : IQueueMessage { - _message = message; - _action = action; - } + private readonly Action _action; + private readonly string _message; - /// - /// Processes the string with the given action - /// - public void Dispatch() - { - if (_action == null || String.IsNullOrEmpty(_message)) - return; + /// + /// Constructor + /// + /// Message to be processed + /// Action to invoke on the message + public ProcessStringMessage(string message, Action action) + { + _message = message; + _action = action; + } - _action(_message); - } + /// + /// Processes the string with the given action + /// + public void Dispatch() + { + if (_action == null || String.IsNullOrEmpty(_message)) + return; - /// - /// To string - /// - /// The current message - /// - /// ToString method - /// - /// - public override string ToString() - { - return _message ?? String.Empty; + _action(_message); + } + + /// + /// To string + /// + /// The current message + /// + /// ToString method + /// + /// + public override string ToString() + { + return _message ?? String.Empty; + } } } - diff --git a/src/PepperDash.Essentials.Core/Queues/StringResponseProcessor.cs b/src/PepperDash.Essentials.Core/Queues/StringResponseProcessor.cs index 44c83e37..207fd597 100644 --- a/src/PepperDash.Essentials.Core/Queues/StringResponseProcessor.cs +++ b/src/PepperDash.Essentials.Core/Queues/StringResponseProcessor.cs @@ -2,104 +2,111 @@ using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Queues; - -public sealed class StringResponseProcessor : IKeyed, IDisposable +namespace PepperDash.Essentials.Core.Queues { - private readonly Action _processStringAction; - private readonly IQueue _queue; - private readonly IBasicCommunication _coms; - private readonly CommunicationGather _gather; - - private StringResponseProcessor(string key, Action processStringAction) + /// + /// Processes string responses from a communication port or gather using a queue to ensure thread safety + /// + public sealed class StringResponseProcessor : IKeyed, IDisposable { - _processStringAction = processStringAction; - _queue = new GenericQueue(key); + private readonly Action _processStringAction; + private readonly IQueue _queue; + private readonly IBasicCommunication _coms; + private readonly CommunicationGather _gather; - CrestronEnvironment.ProgramStatusEventHandler += programEvent => + private StringResponseProcessor(string key, Action processStringAction) { - if (programEvent != eProgramStatusEventType.Stopping) - return; + _processStringAction = processStringAction; + _queue = new GenericQueue(key); - Dispose(); - }; - } - - /// - /// Constructor that builds an instance and subscribes to coms TextReceived for processing - /// - /// Com port to process strings from - /// Action to process the incoming strings - public StringResponseProcessor(IBasicCommunication coms, Action processStringAction) - : this(coms.Key, processStringAction) - { - _coms = coms; - coms.TextReceived += OnResponseReceived; - } - - /// - /// Constructor that builds an instance and subscribes to gather Line Received for processing - /// - /// Gather to process strings from - /// Action to process the incoming strings - public StringResponseProcessor(CommunicationGather gather, Action processStringAction) - : this(gather.Port.Key, processStringAction) - { - _gather = gather; - gather.LineReceived += OnResponseReceived; - } - - private void OnResponseReceived(object sender, GenericCommMethodReceiveTextArgs args) - { - _queue.Enqueue(new ProcessStringMessage(args.Text, _processStringAction)); - } - - /// - /// Key - /// - public string Key - { - get { return _queue.Key; } - } - - /// - /// Disposes the instance and cleans up resources. - /// - public void Dispose() - { - Dispose(true); - CrestronEnvironment.GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (Disposed) - return; - - if (disposing) - { - if (_coms != null) - _coms.TextReceived -= OnResponseReceived; - - if (_gather != null) + CrestronEnvironment.ProgramStatusEventHandler += programEvent => { - _gather.LineReceived -= OnResponseReceived; - _gather.Stop(); - } + if (programEvent != eProgramStatusEventType.Stopping) + return; - _queue.Dispose(); + Dispose(); + }; } - Disposed = true; - } + /// + /// Constructor that builds an instance and subscribes to coms TextReceived for processing + /// + /// Com port to process strings from + /// Action to process the incoming strings + public StringResponseProcessor(IBasicCommunication coms, Action processStringAction) + : this(coms.Key, processStringAction) + { + _coms = coms; + coms.TextReceived += OnResponseReceived; + } - /// - /// If the instance has been disposed or not. If it has, you can not use it anymore - /// - public bool Disposed { get; private set; } + /// + /// Constructor that builds an instance and subscribes to gather Line Received for processing + /// + /// Gather to process strings from + /// Action to process the incoming strings + public StringResponseProcessor(CommunicationGather gather, Action processStringAction) + : this(gather.Port.Key, processStringAction) + { + _gather = gather; + gather.LineReceived += OnResponseReceived; + } - ~StringResponseProcessor() - { - Dispose(false); + private void OnResponseReceived(object sender, GenericCommMethodReceiveTextArgs args) + { + _queue.Enqueue(new ProcessStringMessage(args.Text, _processStringAction)); + } + + /// + /// Key + /// + public string Key + { + get { return _queue.Key; } + } + + /// + /// Disposes the instance and cleans up resources. + /// + public void Dispose() + { + Dispose(true); + CrestronEnvironment.GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (Disposed) + return; + + if (disposing) + { + if (_coms != null) + _coms.TextReceived -= OnResponseReceived; + + if (_gather != null) + { + _gather.LineReceived -= OnResponseReceived; + _gather.Stop(); + } + + _queue.Dispose(); + } + + Disposed = true; + } + + /// + /// Gets or sets the Disposed + /// + public bool Disposed { get; private set; } + + /// + /// Finalizer + /// + ~StringResponseProcessor() + { + Dispose(false); + } } } diff --git a/src/PepperDash.Essentials.Core/Ramps and Increments/ActionIncrementer.cs b/src/PepperDash.Essentials.Core/Ramps and Increments/ActionIncrementer.cs index 952b2acf..544e7d94 100644 --- a/src/PepperDash.Essentials.Core/Ramps and Increments/ActionIncrementer.cs +++ b/src/PepperDash.Essentials.Core/Ramps and Increments/ActionIncrementer.cs @@ -2,146 +2,138 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; +using Crestron.SimplSharp; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// An incrementer that can use the values of some other object/primitive value to do its thing. -/// It uses an Action to set the value and a Func to get the value from whatever this is -/// attached to. -/// -public class ActionIncrementer +namespace PepperDash.Essentials.Core { /// - /// The amount to change the value by each increment + /// An incrementer that can use the values of some other object/primitive value to do its thing. + /// It uses an Action to set the value and a Func to get the value from whatever this is + /// attached to. /// - public int ChangeAmount { get; set; } - /// - /// The maximum value the incrementer can reach - /// - public int MaxValue { get; set; } - /// - /// The minimum value the incrementer can reach - /// - public int MinValue { get; set; } - /// - /// The delay before the incrementer starts repeating - /// - public uint RepeatDelay { get; set; } - /// - /// The time interval between each repeat - /// - public uint RepeatTime { get; set; } - - Action SetAction; - Func GetFunc; - Timer Timer; - - /// - /// - /// - /// - /// - /// - /// - /// - /// Action that will be called when this needs to set the destination value - /// Func that is called to get the current value - public ActionIncrementer(int changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime, Action setAction, Func getFunc) + public class ActionIncrementer { - SetAction = setAction; - GetFunc = getFunc; - ChangeAmount = changeAmount; - MaxValue = maxValue; - MinValue = minValue; - RepeatDelay = repeatDelay; - RepeatTime = repeatTime; - } + /// + /// The amount to change the value by each increment + /// + public int ChangeAmount { get; set; } - /// - /// Starts incrementing cycle - /// - public void StartUp() - { - if (Timer != null) return; - Go(ChangeAmount); - } + /// + /// The maximum value for the incrementer + /// + public int MaxValue { get; set; } - /// - /// Starts decrementing cycle - /// - public void StartDown() - { - if (Timer != null) return; - Go(-ChangeAmount); - } + /// + /// The minimum value for the incrementer + /// + public int MinValue { get; set; } - /// - /// Stops the repeat - /// - public void Stop() - { - if (Timer != null) - Timer.Stop(); - Timer = null; - } + /// + /// The delay before repeating starts + /// + public uint RepeatDelay { get; set; } - /// - /// Helper that does the work of setting new level, and starting repeat loop, checking against bounds first. - /// - /// - void Go(int change) - { - int currentLevel = GetFunc(); - // Fire once then pause - int newLevel = currentLevel + change; - bool atLimit = CheckLevel(newLevel, out newLevel); - SetAction(newLevel); + /// + /// The time between repeats + /// + public uint RepeatTime { get; set; } - if (atLimit) // Don't go past end - Stop(); - else if (Timer == null) // Only enter the timer if it's not already running + Action SetAction; + Func GetFunc; + CTimer Timer; + + /// + /// + /// + /// + /// + /// + /// + /// + /// Action that will be called when this needs to set the destination value + /// Func that is called to get the current value + public ActionIncrementer(int changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime, Action setAction, Func getFunc) { - Timer = new Timer(RepeatDelay) { AutoReset = false }; - Timer.Elapsed += (s, e) => + SetAction = setAction; + GetFunc = getFunc; + ChangeAmount = changeAmount; + MaxValue = maxValue; + MinValue = minValue; + RepeatDelay = repeatDelay; + RepeatTime = repeatTime; + } + + /// + /// StartUp method + /// + public void StartUp() + { + if (Timer != null) return; + Go(ChangeAmount); + } + + /// + /// Starts decrementing cycle + /// + public void StartDown() + { + if (Timer != null) return; + Go(-ChangeAmount); + } + + /// + /// Stops the repeat + /// + public void Stop() + { + if (Timer != null) + Timer.Stop(); + Timer = null; + } + + /// + /// Helper that does the work of setting new level, and starting repeat loop, checking against bounds first. + /// + /// + void Go(int change) + { + int currentLevel = GetFunc(); + // Fire once then pause + int newLevel = currentLevel + change; + bool atLimit = CheckLevel(newLevel, out newLevel); + SetAction(newLevel); + + if (atLimit) // Don't go past end + Stop(); + else if (Timer == null) // Only enter the timer if it's not already running + Timer = new CTimer(o => { Go(change); }, null, RepeatDelay, RepeatTime); + } + + /// + /// Helper to check a new level against min/max. Returns revised level if new level + /// will go out of bounds + /// + /// The level to check against bounds + /// Revised level if bounds are exceeded. Min or max + /// true if new level is at or past bounds + bool CheckLevel(int levelIn, out int levelOut) + { + bool isAtLimit = false; + if (levelIn > MaxValue) { - Go(change); - if (Timer != null) - { - Timer.Interval = RepeatTime; - Timer.AutoReset = true; - Timer.Start(); - } - }; - Timer.Start(); + levelOut = MaxValue; + isAtLimit = true; + } + else if (levelIn < MinValue) + { + levelOut = MinValue; + isAtLimit = true; + } + else + levelOut = levelIn; + return isAtLimit; } } - - /// - /// Helper to check a new level against min/max. Returns revised level if new level - /// will go out of bounds - /// - /// The level to check against bounds - /// Revised level if bounds are exceeded. Min or max - /// true if new level is at or past bounds - bool CheckLevel(int levelIn, out int levelOut) - { - bool isAtLimit = false; - if (levelIn > MaxValue) - { - levelOut = MaxValue; - isAtLimit = true; - } - else if (levelIn < MinValue) - { - levelOut = MinValue; - isAtLimit = true; - } - else - levelOut = levelIn; - return isAtLimit; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Ramps and Increments/NumericalHelpers.cs b/src/PepperDash.Essentials.Core/Ramps and Increments/NumericalHelpers.cs index 4feca569..383593f4 100644 --- a/src/PepperDash.Essentials.Core/Ramps and Increments/NumericalHelpers.cs +++ b/src/PepperDash.Essentials.Core/Ramps and Increments/NumericalHelpers.cs @@ -4,39 +4,40 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Helper methods for numerical operations -/// -public class NumericalHelpers -{ +namespace PepperDash.Essentials.Core +{ /// - /// Scales a value + /// Represents a NumericalHelpers /// - /// - /// - /// - /// - /// - /// - public static double Scale(double input, double inMin, double inMax, double outMin, double outMax) - { - //Debug.LogMessage(LogEventLevel.Verbose, this, "Scaling (double) input '{0}' with min '{1}'/max '{2}' to output range min '{3}'/max '{4}'", input, inMin, inMax, outMin, outMax); - - double inputRange = inMax - inMin; - - if (inputRange <= 0) + public class NumericalHelpers + { + /// + /// Scales a value + /// + /// + /// + /// + /// + /// + /// + public static double Scale(double input, double inMin, double inMax, double outMin, double outMax) { - throw new ArithmeticException(string.Format("Invalid Input Range '{0}' for Scaling. Min '{1}' Max '{2}'.", inputRange, inMin, inMax)); + //Debug.LogMessage(LogEventLevel.Verbose, this, "Scaling (double) input '{0}' with min '{1}'/max '{2}' to output range min '{3}'/max '{4}'", input, inMin, inMax, outMin, outMax); + + double inputRange = inMax - inMin; + + if (inputRange <= 0) + { + throw new ArithmeticException(string.Format("Invalid Input Range '{0}' for Scaling. Min '{1}' Max '{2}'.", inputRange, inMin, inMax)); + } + + double outputRange = outMax - outMin; + + var output = (((input - inMin) * outputRange) / inputRange) + outMin; + + // Debug.LogMessage(LogEventLevel.Verbose, this, "Scaled output '{0}'", output); + + return output; } - - double outputRange = outMax - outMin; - - var output = (((input - inMin) * outputRange) / inputRange) + outMin; - - // Debug.LogMessage(LogEventLevel.Verbose, this, "Scaled output '{0}'", output); - - return output; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Ramps and Increments/UshortSigIncrementer.cs b/src/PepperDash.Essentials.Core/Ramps and Increments/UshortSigIncrementer.cs index eae6c21f..02c40f69 100644 --- a/src/PepperDash.Essentials.Core/Ramps and Increments/UshortSigIncrementer.cs +++ b/src/PepperDash.Essentials.Core/Ramps and Increments/UshortSigIncrementer.cs @@ -2,152 +2,139 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// Attaches to UShortInputSig and does incremental ramping of the signal -/// -public class UshortSigIncrementer +namespace PepperDash.Essentials.Core { - UShortInputSig TheSig; - /// - /// Amount to change the signal on each step + /// Attaches to UShortInputSig and does incremental ramping of the signal /// - public ushort ChangeAmount { get; set; } - /// - /// Maximum value to ramp to - /// - public int MaxValue { get; set; } - /// - /// Minimum value to ramp to - /// - public int MinValue { get; set; } - /// - /// The delay before the incrementer starts repeating - /// - public uint RepeatDelay { get; set; } - /// - /// The time interval between each repeat - /// - public uint RepeatTime { get; set; } - bool SignedMode; - Timer Timer; - - /// - /// Constructor - /// - /// - /// - /// - /// - /// - /// - public UshortSigIncrementer(UShortInputSig sig, ushort changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime) + public class UshortSigIncrementer { - TheSig = sig; - ChangeAmount = changeAmount; - MaxValue = maxValue; - MinValue = minValue; - if (MinValue < 0 || MaxValue < 0) SignedMode = true; - RepeatDelay = repeatDelay; - RepeatTime = repeatTime; - if (SignedMode && (MinValue < -32768 || MaxValue > 32767)) - Debug.LogMessage(LogEventLevel.Debug, "UshortSigIncrementer has signed values that exceed range of -32768, 32767"); - } + UShortInputSig TheSig; - /// - /// Starts incrementing cycle - /// + /// + /// The amount to change the value by each increment + /// + public ushort ChangeAmount { get; set; } - public void StartUp() - { - if (Timer != null) return; - Go(ChangeAmount); - } + /// + /// The maximum value for the incrementer + /// + public int MaxValue { get; set; } - /// - /// Starts decrementing cycle - /// - public void StartDown() - { - if (Timer != null) return; - Go(-ChangeAmount); - } + /// + /// The minimum value for the incrementer + /// + public int MinValue { get; set; } - void Go(int change) - { - int level; - if (SignedMode) level = TheSig.ShortValue; - else level = TheSig.UShortValue; + /// + /// The delay before repeating starts + /// + public uint RepeatDelay { get; set; } - // Fire once then pause - int newLevel = level + change; - bool atLimit = CheckLevel(newLevel, out newLevel); - SetValue((ushort)newLevel); + /// + /// The time between repeats + /// + public uint RepeatTime { get; set; } + bool SignedMode; + CTimer Timer; - if (atLimit) // Don't go past end - Stop(); - else if (Timer == null) // Only enter the timer if it's not already running + /// + /// Constructor + /// + /// the signal toi be incremented + /// the amount to increment by + /// the minimum value of the signal + /// the maximum value of the signal + /// the delay before repeating starts + /// the time between repeats + public UshortSigIncrementer(UShortInputSig sig, ushort changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime) { - Timer = new Timer(RepeatDelay) { AutoReset = false }; - Timer.Elapsed += (s, e) => + TheSig = sig; + ChangeAmount = changeAmount; + MaxValue = maxValue; + MinValue = minValue; + if (MinValue < 0 || MaxValue < 0) SignedMode = true; + RepeatDelay = repeatDelay; + RepeatTime = repeatTime; + if (SignedMode && (MinValue < -32768 || MaxValue > 32767)) + Debug.LogMessage(LogEventLevel.Debug, "UshortSigIncrementer has signed values that exceed range of -32768, 32767"); + } + + /// + /// StartUp method + /// + public void StartUp() + { + if (Timer != null) return; + Go(ChangeAmount); + } + + /// + /// StartDown method + /// + public void StartDown() + { + if (Timer != null) return; + Go(-ChangeAmount); + } + + void Go(int change) + { + int level; + if (SignedMode) level = TheSig.ShortValue; + else level = TheSig.UShortValue; + + // Fire once then pause + int newLevel = level + change; + bool atLimit = CheckLevel(newLevel, out newLevel); + SetValue((ushort)newLevel); + + + if (atLimit) // Don't go past end + Stop(); + else if (Timer == null) // Only enter the timer if it's not already running + Timer = new CTimer(o => { Go(change); }, null, RepeatDelay, RepeatTime); + } + + bool CheckLevel(int levelIn, out int levelOut) + { + bool IsAtLimit = false; + if (levelIn > MaxValue) { - Go(change); - if (Timer != null) - { - Timer.Interval = RepeatTime; - Timer.AutoReset = true; - Timer.Start(); - } - }; - Timer.Start(); + levelOut = MaxValue; + IsAtLimit = true; + } + else if (levelIn < MinValue) + { + levelOut = MinValue; + IsAtLimit = true; + } + else + levelOut = levelIn; + return IsAtLimit; } - } - bool CheckLevel(int levelIn, out int levelOut) - { - bool IsAtLimit = false; - if (levelIn > MaxValue) + /// + /// Stop method + /// + public void Stop() { - levelOut = MaxValue; - IsAtLimit = true; + if (Timer != null) + Timer.Stop(); + Timer = null; } - else if (levelIn < MinValue) + + void SetValue(ushort value) { - levelOut = MinValue; - IsAtLimit = true; + //CrestronConsole.PrintLine("Increment level:{0} / {1}", value, (short)value); + TheSig.UShortValue = value; } - else - levelOut = levelIn; - return IsAtLimit; - } - - /// - /// Stops incrementing/decrementing cycle - /// - public void Stop() - { - if (Timer != null) - Timer.Stop(); - Timer = null; - } - - /// - /// Sets the value of the signal - /// - /// - - void SetValue(ushort value) - { - //CrestronConsole.PrintLine("Increment level:{0} / {1}", value, (short)value); - TheSig.UShortValue = value; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs b/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs index c446bc98..601c3559 100644 --- a/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs +++ b/src/PepperDash.Essentials.Core/Room/Behaviours/RoomOnToDefaultSourceWhenOccupied.cs @@ -16,541 +16,613 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Devices; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// A device that when linked to a room can power the room on when enabled during scheduled hours. -/// -public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice +namespace PepperDash.Essentials.Core { - RoomOnToDefaultSourceWhenOccupiedConfig PropertiesConfig; - - public bool FeatureEnabled { get; private set; } - - public DateTime FeatureEnabledTime { get; private set; } - - ScheduledEvent FeatureEnableEvent; - - const string FeatureEnableEventName = "EnableRoomOnToDefaultSourceWhenOccupied"; - - public DateTime FeatureDisabledTime { get; private set; } - - ScheduledEvent FeatureDisableEvent; - - const string FeatureDisableEventName = "DisableRoomOnToDefaultSourceWhenOccupied"; - - ScheduledEventGroup FeatureEventGroup; - - public IRoomOccupancy Room { get; private set; } - - private Fusion.IEssentialsRoomFusionController FusionRoom; - - public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) : - base (config) + /// + /// A device that when linked to a room can power the room on when enabled during scheduled hours. + /// + public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice { - PropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + RoomOnToDefaultSourceWhenOccupiedConfig PropertiesConfig; - FeatureEventGroup = new ScheduledEventGroup(this.Key); + /// + /// Gets or sets the FeatureEnabled + /// + public bool FeatureEnabled { get; private set; } - FeatureEventGroup.RetrieveAllEvents(); + /// + /// Gets or sets the FeatureEnabledTime + /// + public DateTime FeatureEnabledTime { get; private set; } - // Add to the global class for tracking - Scheduler.AddEventGroup(FeatureEventGroup); + ScheduledEvent FeatureEnableEvent; - AddPostActivationAction(() => + const string FeatureEnableEventName = "EnableRoomOnToDefaultSourceWhenOccupied"; + + /// + /// Gets or sets the FeatureDisabledTime + /// + public DateTime FeatureDisabledTime { get; private set; } + + ScheduledEvent FeatureDisableEvent; + + const string FeatureDisableEventName = "DisableRoomOnToDefaultSourceWhenOccupied"; + + ScheduledEventGroup FeatureEventGroup; + + /// + /// Gets or sets the Room + /// + public IRoomOccupancy Room { get; private set; } + + private Fusion.IEssentialsRoomFusionController FusionRoom; + + /// + /// Constructor for RoomOnToDefaultSourceWhenOccupied + /// + /// config of the device + public RoomOnToDefaultSourceWhenOccupied(DeviceConfig config) : + base (config) { - // Subscribe to room event to know when RoomOccupancy is set and ready to be subscribed to + PropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); + + FeatureEventGroup = new ScheduledEventGroup(this.Key); + + FeatureEventGroup.RetrieveAllEvents(); + + // Add to the global class for tracking + Scheduler.AddEventGroup(FeatureEventGroup); + + AddPostActivationAction(() => + { + // Subscribe to room event to know when RoomOccupancy is set and ready to be subscribed to + if (Room != null) + Room.RoomOccupancyIsSet += new EventHandler(RoomOccupancyIsSet); + + else + Debug.LogMessage(LogEventLevel.Debug, this, "Room has no RoomOccupancy object set"); + + var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion"; + + FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.IEssentialsRoomFusionController; + + if (FusionRoom == null) + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey); + }); + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + SetUpDevice(); + + return base.CustomActivate(); + } + + /// + /// Sets up device based on config values + /// + void SetUpDevice() + { + Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as IRoomOccupancy; + if (Room != null) - Room.RoomOccupancyIsSet += new EventHandler(RoomOccupancyIsSet); + { + try + { + FeatureEnabledTime = DateTime.Parse(PropertiesConfig.OccupancyStartTime); + if (FeatureEnabledTime != null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Enabled Time: {0}", FeatureEnabledTime.ToString()); + } + else + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyStartTime); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", PropertiesConfig.OccupancyStartTime, e); + } + + try + { + FeatureDisabledTime = DateTime.Parse(PropertiesConfig.OccupancyEndTime); + + if (FeatureDisabledTime != null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Disabled Time: {0}", FeatureDisabledTime.ToString()); + } + else + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyEndTime); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse a DateTime config value \n Error: {1}", e); + } + + if (!PropertiesConfig.EnableRoomOnWhenOccupied) + FeatureEventGroup.ClearAllEvents(); + else + { + AddEnableEventToGroup(); + + AddDisableEventToGroup(); + + FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack); + + FeatureEventGroup.EnableAllEvents(); + } + + FeatureEnabled = CheckIfFeatureShouldBeEnabled(); + } else - Debug.LogMessage(LogEventLevel.Debug, this, "Room has no RoomOccupancy object set"); + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get room from Device Manager with key: {0}", PropertiesConfig.RoomKey); + } - var fusionRoomKey = PropertiesConfig.RoomKey + "-fusion"; - - FusionRoom = DeviceManager.GetDeviceForKey(fusionRoomKey) as Core.Fusion.IEssentialsRoomFusionController; - - if (FusionRoom == null) - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get Fusion Room from Device Manager with key: {0}", fusionRoomKey); - }); - } - - protected override bool CustomActivate() - { - SetUpDevice(); - - return base.CustomActivate(); - } - - /// - /// Sets up device based on config values - /// - void SetUpDevice() - { - Room = DeviceManager.GetDeviceForKey(PropertiesConfig.RoomKey) as IRoomOccupancy; - - if (Room != null) + /// + /// CustomSetConfig method + /// + /// config of the device + protected override void CustomSetConfig(DeviceConfig config) { - try - { - FeatureEnabledTime = DateTime.Parse(PropertiesConfig.OccupancyStartTime); + var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); - if (FeatureEnabledTime != null) + if(newPropertiesConfig != null) + PropertiesConfig = newPropertiesConfig; + + ConfigWriter.UpdateDeviceConfig(config); + + SetUpDevice(); + } + + /// + /// Subscribe to feedback from RoomIsOccupiedFeedback on Room + /// + /// + /// + void RoomOccupancyIsSet(object sender, EventArgs e) + { + if (Room.RoomOccupancy != null) + { + Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; + Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); + Debug.LogMessage(LogEventLevel.Debug, this, "Subscribed to RoomOccupancy status from: '{0}'", Room.Key); + } + } + + void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + Debug.LogMessage(LogEventLevel.Debug, this, "{0}:{1} @ {2}", SchEvent.Name, type, DateTime.Now); + + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + SchEvent.Acknowledge(); + + if (SchEvent.Name == FeatureEnableEventName) { - Debug.LogMessage(LogEventLevel.Debug, this, "Enabled Time: {0}", FeatureEnabledTime.ToString()); + + if (PropertiesConfig.EnableRoomOnWhenOccupied) + FeatureEnabled = true; + + Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Enabled by event.*****"); } - else - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyStartTime); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse OccupancyStartTime property: {0} \n Error: {1}", PropertiesConfig.OccupancyStartTime, e); - } - - try - { - FeatureDisabledTime = DateTime.Parse(PropertiesConfig.OccupancyEndTime); - - if (FeatureDisabledTime != null) + else if (SchEvent.Name == FeatureDisableEventName) { - Debug.LogMessage(LogEventLevel.Debug, this, "Disabled Time: {0}", FeatureDisabledTime.ToString()); + FeatureEnabled = false; + + Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Disabled by event.*****"); } - else - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse {0} to DateTime", PropertiesConfig.OccupancyEndTime); } - catch (Exception e) + } + + /// + /// Checks if the feature should be currently enabled. Used on startup if processor starts after start time but before end time + /// + /// + bool CheckIfFeatureShouldBeEnabled() + { + bool enabled = false; + + if(PropertiesConfig.EnableRoomOnWhenOccupied) { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to parse a DateTime config value \n Error: {1}", e); + Debug.LogMessage(LogEventLevel.Debug, this, "Current Time: {0} \n FeatureEnabledTime: {1} \n FeatureDisabledTime: {2}", DateTime.Now, FeatureEnabledTime, FeatureDisabledTime); + + if (DateTime.Now.TimeOfDay.CompareTo(FeatureEnabledTime.TimeOfDay) >= 0 && FeatureDisabledTime.TimeOfDay.CompareTo(DateTime.Now.TimeOfDay) > 0) + { + if (SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(DateTime.Now, CalculateDaysOfWeekRecurrence())) + { + enabled = true; + } + } } - if (!PropertiesConfig.EnableRoomOnWhenOccupied) - FeatureEventGroup.ClearAllEvents(); + if(enabled) + Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Enabled*****"); + else + Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Disabled*****"); + + return enabled; + } + + /// + /// Respond to Occupancy status event + /// + /// + /// + void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + Debug.LogMessage(LogEventLevel.Debug, this, "RoomIsOccupiedFeeback.OutputChange event fired. e.BoolValue: {0}", e.BoolValue); + if(e.BoolValue) + { + // Occupancy detected + + if (FeatureEnabled) + { + var essentialsRoom = Room as IEssentialsRoom; + + if (essentialsRoom != null) { + if (!essentialsRoom.OnFeedback.BoolValue) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Powering Room on to default source"); + + var defaultRouteRoom = Room as IRunDefaultPresentRoute; + + if (defaultRouteRoom != null) + { + defaultRouteRoom.RunDefaultPresentRoute(); + } + } + } + // Check room power state first + + } + } + } + + void CreateEvent(ScheduledEvent schEvent, string name) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Adding Event: '{0}'", name); + // Create the event + if (schEvent == null) + schEvent = new ScheduledEvent(name, FeatureEventGroup); + + // Set up its initial properties + + schEvent.Acknowledgeable = false; + + if(!schEvent.Persistent) + schEvent.Persistent = true; + + schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); + + // Set config driven properties + + if (schEvent.Name == FeatureEnableEventName) + { + schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature"; + + var eventRecurrennce = CalculateDaysOfWeekRecurrence(); + + var eventTime = new DateTime(); + + // Check to make sure the date for this event is in the future + if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0) + eventTime = FeatureEnabledTime.AddDays(1); + else + eventTime = FeatureEnabledTime; + + Debug.LogMessage(LogEventLevel.Debug, this, "eventTime (before recurrence check): {0}", eventTime); + + // Check day of week against recurrence days and move date ahead as necessary to avoid throwing an exception by trying to set the event + // start date on a day of the week that doesn't match teh recurrence values + while(!SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(eventTime, eventRecurrennce)) + { + eventTime = eventTime.AddDays(1); + Debug.LogMessage(LogEventLevel.Debug, this, "eventTime does not fall on a recurrence weekday. eventTime: {0}", eventTime); + } + + schEvent.DateAndTime.SetAbsoluteEventTime(eventTime); + + Debug.LogMessage(LogEventLevel.Debug, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); + + //CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime); + + schEvent.Recurrence.Weekly(eventRecurrennce); + + } + else if (schEvent.Name == FeatureDisableEventName) + { + schEvent.Description = "Disables the RoomOnToDefaultSourceWhenOccupiedFeature"; + + // Check to make sure the date for this event is in the future + if (DateTime.Now.CompareTo(FeatureDisabledTime) > 0) + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime.AddDays(1)); + else + schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime); + + Debug.LogMessage(LogEventLevel.Debug, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); + + CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureDisabledTime, FeatureEnabledTime); + + schEvent.Recurrence.Daily(); + } + } + + void CalculateAndSetAcknowledgeExpirationTimeout(ScheduledEvent schEvent, DateTime time1, DateTime time2) + { + Debug.LogMessage(LogEventLevel.Debug, this, "time1.Hour = {0}", time1.Hour); + Debug.LogMessage(LogEventLevel.Debug, this, "time2.Hour = {0}", time2.Hour); + Debug.LogMessage(LogEventLevel.Debug, this, "time1.Minute = {0}", time1.Minute); + Debug.LogMessage(LogEventLevel.Debug, this, "time2.Minute = {0}", time2.Minute); + + // Calculate the Acknowledge Expiration timer to be the time between the enable and dispable events, less one minute + var ackHours = time2.Hour - time1.Hour; + if(ackHours < 0) + ackHours = ackHours + 24; + var ackMinutes = time2.Minute - time1.Minute; + + Debug.LogMessage(LogEventLevel.Debug, this, "ackHours = {0}, ackMinutes = {1}", ackHours, ackMinutes); + + var ackTotalMinutes = ((ackHours * 60) + ackMinutes) - 1; + + var ackExpHour = ackTotalMinutes / 60; + var ackExpMinutes = ackTotalMinutes % 60; + + Debug.LogMessage(LogEventLevel.Debug, this, "Acknowledge Expiration Timeout: {0} hours, {1} minutes", ackExpHour, ackExpMinutes); + + schEvent.AcknowledgeExpirationTimeout.Hour = (ushort)(ackHours); + schEvent.AcknowledgeExpirationTimeout.Minute = (ushort)(ackExpMinutes); + } + + /// + /// Checks existing event to see if it matches the execution time + /// + /// event we are checking + /// time we are checking against + /// + bool CheckExistingEventTimeForMatch(ScheduledEvent existingEvent, DateTime newTime) + { + bool isMatch = true; + + // Check to see if hour and minute match + if (existingEvent.DateAndTime.Hour != newTime.Hour || existingEvent.DateAndTime.Minute != newTime.Minute) + return false; + + + return isMatch; + } + + /// + /// Checks existing event to see if it matches the recurrence days + /// + /// + /// + /// + bool CheckExistingEventRecurrenceForMatch(ScheduledEvent existingEvent, ScheduledEventCommon.eWeekDays eWeekdays) + { + bool isMatch = true; + + // Check to see if recurrence matches + if (eWeekdays != existingEvent.Recurrence.RecurrenceDays) + return false; + + return isMatch; + } + + /// + /// Adds the Enable event to the local event group and sets its properties based on config + /// + void AddEnableEventToGroup() + { + if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureEnableEventName)) + { + CreateEvent(FeatureEnableEvent, FeatureEnableEventName); + } else { - AddEnableEventToGroup(); + // Check if existing event has same time and recurrence as config values - AddDisableEventToGroup(); + FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName]; + Debug.LogMessage(LogEventLevel.Debug, this, "Enable event already found in group"); - FeatureEventGroup.UserGroupCallBack += new ScheduledEventGroup.UserEventGroupCallBack(FeatureEventGroup_UserGroupCallBack); + // Check config times and days against DateAndTime of existing event. If different, delete existing event and create new event + if(!CheckExistingEventTimeForMatch(FeatureEnableEvent, FeatureEnabledTime) || !CheckExistingEventRecurrenceForMatch(FeatureEnableEvent, CalculateDaysOfWeekRecurrence())) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureEnableEvent.Name); + FeatureEventGroup.DeleteEvent(FeatureEnableEvent); - FeatureEventGroup.EnableAllEvents(); + FeatureEnableEvent = null; + + CreateEvent(FeatureEnableEvent, FeatureEnableEventName); + } } - FeatureEnabled = CheckIfFeatureShouldBeEnabled(); } - else - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to get room from Device Manager with key: {0}", PropertiesConfig.RoomKey); - } - - protected override void CustomSetConfig(DeviceConfig config) - { - var newPropertiesConfig = JsonConvert.DeserializeObject(config.Properties.ToString()); - - if(newPropertiesConfig != null) - PropertiesConfig = newPropertiesConfig; - - ConfigWriter.UpdateDeviceConfig(config); - - SetUpDevice(); - } - - /// - /// Subscribe to feedback from RoomIsOccupiedFeedback on Room - /// - /// - /// - void RoomOccupancyIsSet(object sender, EventArgs e) - { - if (Room.RoomOccupancy != null) + /// + /// Adds the Enable event to the local event group and sets its properties based on config + /// + void AddDisableEventToGroup() { - Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; - Room.RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += new EventHandler(RoomIsOccupiedFeedback_OutputChange); - Debug.LogMessage(LogEventLevel.Debug, this, "Subscribed to RoomOccupancy status from: '{0}'", Room.Key); - } - } - - void FeatureEventGroup_UserGroupCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) - { - Debug.LogMessage(LogEventLevel.Debug, this, "{0}:{1} @ {2}", SchEvent.Name, type, DateTime.Now); - - if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) - { - SchEvent.Acknowledge(); - - if (SchEvent.Name == FeatureEnableEventName) + if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureDisableEventName)) { - - if (PropertiesConfig.EnableRoomOnWhenOccupied) + CreateEvent(FeatureDisableEvent, FeatureDisableEventName); + } + else + { + FeatureDisableEvent = FeatureEventGroup.ScheduledEvents[FeatureDisableEventName]; + Debug.LogMessage(LogEventLevel.Debug, this, "Disable event already found in group"); + + // Check config times against DateAndTime of existing event. If different, delete existing event and create new event + if(!CheckExistingEventTimeForMatch(FeatureDisableEvent, FeatureDisabledTime)) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureDisableEvent.Name); + + FeatureEventGroup.DeleteEvent(FeatureDisableEvent); + + FeatureDisableEvent = null; + + CreateEvent(FeatureDisableEvent, FeatureDisableEventName); + } + } + } + + + /// + /// Calculates the correct bitfield enum value for the event recurrence based on the config values + /// + /// + ScheduledEventCommon.eWeekDays CalculateDaysOfWeekRecurrence() + { + ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays(); + + if (PropertiesConfig.EnableSunday) + value = value | ScheduledEventCommon.eWeekDays.Sunday; + if (PropertiesConfig.EnableMonday) + value = value | ScheduledEventCommon.eWeekDays.Monday; + if (PropertiesConfig.EnableTuesday) + value = value | ScheduledEventCommon.eWeekDays.Tuesday; + if (PropertiesConfig.EnableWednesday) + value = value | ScheduledEventCommon.eWeekDays.Wednesday; + if (PropertiesConfig.EnableThursday) + value = value | ScheduledEventCommon.eWeekDays.Thursday; + if (PropertiesConfig.EnableFriday) + value = value | ScheduledEventCommon.eWeekDays.Friday; + if (PropertiesConfig.EnableSaturday) + value = value | ScheduledEventCommon.eWeekDays.Saturday; + + return value; + } + + /// + /// Callback for event that enables feature. Enables feature if config property is true + /// + /// + /// + void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) + { + if(PropertiesConfig.EnableRoomOnWhenOccupied) FeatureEnabled = true; - Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Enabled by event.*****"); + Debug.LogMessage(LogEventLevel.Debug, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled."); } - else if (SchEvent.Name == FeatureDisableEventName) + } + + /// + /// Callback for event that enables feature. Disables feature + /// + /// + /// + void FeatureDisableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) + { + if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) { FeatureEnabled = false; - Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Disabled by event.*****"); + Debug.LogMessage(LogEventLevel.Debug, this, "RoomOnToDefaultSourceWhenOccupied Feature Disabled."); } } } /// - /// Checks if the feature should be currently enabled. Used on startup if processor starts after start time but before end time + /// Represents a RoomOnToDefaultSourceWhenOccupiedConfig /// - /// - bool CheckIfFeatureShouldBeEnabled() + public class RoomOnToDefaultSourceWhenOccupiedConfig { - bool enabled = false; + /// + /// Gets or sets the RoomKey + /// + [JsonProperty("roomKey")] + public string RoomKey { get; set; } - if(PropertiesConfig.EnableRoomOnWhenOccupied) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Current Time: {0} \n FeatureEnabledTime: {1} \n FeatureDisabledTime: {2}", DateTime.Now, FeatureEnabledTime, FeatureDisabledTime); + /// + /// Gets or sets the EnableRoomOnWhenOccupied + /// + [JsonProperty("enableRoomOnWhenOccupied")] + public bool EnableRoomOnWhenOccupied { get; set; } - if (DateTime.Now.TimeOfDay.CompareTo(FeatureEnabledTime.TimeOfDay) >= 0 && FeatureDisabledTime.TimeOfDay.CompareTo(DateTime.Now.TimeOfDay) > 0) - { - if (SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(DateTime.Now, CalculateDaysOfWeekRecurrence())) - { - enabled = true; - } - } - } + /// + /// Gets or sets the OccupancyStartTime + /// + [JsonProperty("occupancyStartTime")] + public string OccupancyStartTime { get; set; } - if(enabled) - Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Enabled*****"); - else - Debug.LogMessage(LogEventLevel.Debug, this, "*****Feature Disabled*****"); + /// + /// Gets or sets the OccupancyEndTime + /// + [JsonProperty("occupancyEndTime")] + public string OccupancyEndTime { get; set; } - return enabled; + /// + /// Gets or sets the EnableSunday + /// + [JsonProperty("enableSunday")] + public bool EnableSunday { get; set; } + + /// + /// Gets or sets the EnableMonday + /// + [JsonProperty("enableMonday")] + public bool EnableMonday { get; set; } + + /// + /// Gets or sets the EnableTuesday + /// + [JsonProperty("enableWednesday")] + public bool EnableTuesday { get; set; } + + /// + /// Gets or sets the EnableWednesday + /// + [JsonProperty("enableWednesday")] + public bool EnableWednesday { get; set; } + + /// + /// Gets or sets the EnableThursday + /// + [JsonProperty("enableThursday")] + public bool EnableThursday { get; set; } + + /// + /// Gets or sets the EnableFriday + /// + [JsonProperty("enableFriday")] + public bool EnableFriday { get; set; } + + /// + /// Gets or sets the EnableSaturday + /// + [JsonProperty("enableSaturday")] + public bool EnableSaturday { get; set; } } /// - /// Respond to Occupancy status event + /// Represents a RoomOnToDefaultSourceWhenOccupiedFactory /// - /// - /// - void RoomIsOccupiedFeedback_OutputChange(object sender, FeedbackEventArgs e) + public class RoomOnToDefaultSourceWhenOccupiedFactory : EssentialsDeviceFactory { - Debug.LogMessage(LogEventLevel.Debug, this, "RoomIsOccupiedFeeback.OutputChange event fired. e.BoolValue: {0}", e.BoolValue); - if(e.BoolValue) + /// + /// Constructor for RoomOnToDefaultSourceWhenOccupiedFactory + /// + public RoomOnToDefaultSourceWhenOccupiedFactory() { - // Occupancy detected + TypeNames = new List() { "roomonwhenoccupancydetectedfeature" }; + } - if (FeatureEnabled) - { - var essentialsRoom = Room as IEssentialsRoom; - - if (essentialsRoom != null) { - if (!essentialsRoom.OnFeedback.BoolValue) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Powering Room on to default source"); - - var defaultRouteRoom = Room as IRunDefaultPresentRoute; - - if (defaultRouteRoom != null) - { - defaultRouteRoom.RunDefaultPresentRoute(); - } - } - } - // Check room power state first - - } + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RoomOnToDefaultSourceWhenOccupied Device"); + return new RoomOnToDefaultSourceWhenOccupied(dc); } } - void CreateEvent(ScheduledEvent schEvent, string name) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Adding Event: '{0}'", name); - // Create the event - if (schEvent == null) - schEvent = new ScheduledEvent(name, FeatureEventGroup); - - // Set up its initial properties - - schEvent.Acknowledgeable = false; - - if(!schEvent.Persistent) - schEvent.Persistent = true; - - schEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday); - - // Set config driven properties - - if (schEvent.Name == FeatureEnableEventName) - { - schEvent.Description = "Enables the RoomOnToDefaultSourceWhenOccupiedFeature"; - - var eventRecurrennce = CalculateDaysOfWeekRecurrence(); - - var eventTime = new DateTime(); - - // Check to make sure the date for this event is in the future - if (DateTime.Now.CompareTo(FeatureEnabledTime) > 0) - eventTime = FeatureEnabledTime.AddDays(1); - else - eventTime = FeatureEnabledTime; - - Debug.LogMessage(LogEventLevel.Debug, this, "eventTime (before recurrence check): {0}", eventTime); - - // Check day of week against recurrence days and move date ahead as necessary to avoid throwing an exception by trying to set the event - // start date on a day of the week that doesn't match teh recurrence values - while(!SchedulerUtilities.CheckIfDayOfWeekMatchesRecurrenceDays(eventTime, eventRecurrennce)) - { - eventTime = eventTime.AddDays(1); - Debug.LogMessage(LogEventLevel.Debug, this, "eventTime does not fall on a recurrence weekday. eventTime: {0}", eventTime); - } - - schEvent.DateAndTime.SetAbsoluteEventTime(eventTime); - - Debug.LogMessage(LogEventLevel.Debug, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); - - //CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureEnabledTime, FeatureDisabledTime); - - schEvent.Recurrence.Weekly(eventRecurrennce); - - } - else if (schEvent.Name == FeatureDisableEventName) - { - schEvent.Description = "Disables the RoomOnToDefaultSourceWhenOccupiedFeature"; - - // Check to make sure the date for this event is in the future - if (DateTime.Now.CompareTo(FeatureDisabledTime) > 0) - schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime.AddDays(1)); - else - schEvent.DateAndTime.SetAbsoluteEventTime(FeatureDisabledTime); - - Debug.LogMessage(LogEventLevel.Debug, this, "Event '{0}' Absolute time set to {1}", schEvent.Name, schEvent.DateAndTime.ToString()); - - CalculateAndSetAcknowledgeExpirationTimeout(schEvent, FeatureDisabledTime, FeatureEnabledTime); - - schEvent.Recurrence.Daily(); - } - } - - void CalculateAndSetAcknowledgeExpirationTimeout(ScheduledEvent schEvent, DateTime time1, DateTime time2) - { - Debug.LogMessage(LogEventLevel.Debug, this, "time1.Hour = {0}", time1.Hour); - Debug.LogMessage(LogEventLevel.Debug, this, "time2.Hour = {0}", time2.Hour); - Debug.LogMessage(LogEventLevel.Debug, this, "time1.Minute = {0}", time1.Minute); - Debug.LogMessage(LogEventLevel.Debug, this, "time2.Minute = {0}", time2.Minute); - - // Calculate the Acknowledge Expiration timer to be the time between the enable and dispable events, less one minute - var ackHours = time2.Hour - time1.Hour; - if(ackHours < 0) - ackHours = ackHours + 24; - var ackMinutes = time2.Minute - time1.Minute; - - Debug.LogMessage(LogEventLevel.Debug, this, "ackHours = {0}, ackMinutes = {1}", ackHours, ackMinutes); - - var ackTotalMinutes = ((ackHours * 60) + ackMinutes) - 1; - - var ackExpHour = ackTotalMinutes / 60; - var ackExpMinutes = ackTotalMinutes % 60; - - Debug.LogMessage(LogEventLevel.Debug, this, "Acknowledge Expiration Timeout: {0} hours, {1} minutes", ackExpHour, ackExpMinutes); - - schEvent.AcknowledgeExpirationTimeout.Hour = (ushort)(ackHours); - schEvent.AcknowledgeExpirationTimeout.Minute = (ushort)(ackExpMinutes); - } - - /// - /// Checks existing event to see if it matches the execution time - /// - /// - /// - bool CheckExistingEventTimeForMatch(ScheduledEvent existingEvent, DateTime newTime) - { - bool isMatch = true; - - // Check to see if hour and minute match - if (existingEvent.DateAndTime.Hour != newTime.Hour || existingEvent.DateAndTime.Minute != newTime.Minute) - return false; - - - return isMatch; - } - - /// - /// Checks existing event to see if it matches the recurrence days - /// - /// - /// - /// - bool CheckExistingEventRecurrenceForMatch(ScheduledEvent existingEvent, ScheduledEventCommon.eWeekDays eWeekdays) - { - bool isMatch = true; - - // Check to see if recurrence matches - if (eWeekdays != existingEvent.Recurrence.RecurrenceDays) - return false; - - return isMatch; - } - - /// - /// Adds the Enable event to the local event group and sets its properties based on config - /// - void AddEnableEventToGroup() - { - if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureEnableEventName)) - { - CreateEvent(FeatureEnableEvent, FeatureEnableEventName); - } - else - { - // Check if existing event has same time and recurrence as config values - - FeatureEnableEvent = FeatureEventGroup.ScheduledEvents[FeatureEnableEventName]; - Debug.LogMessage(LogEventLevel.Debug, this, "Enable event already found in group"); - - // Check config times and days against DateAndTime of existing event. If different, delete existing event and create new event - if(!CheckExistingEventTimeForMatch(FeatureEnableEvent, FeatureEnabledTime) || !CheckExistingEventRecurrenceForMatch(FeatureEnableEvent, CalculateDaysOfWeekRecurrence())) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureEnableEvent.Name); - FeatureEventGroup.DeleteEvent(FeatureEnableEvent); - - FeatureEnableEvent = null; - - CreateEvent(FeatureEnableEvent, FeatureEnableEventName); - } - } - - } - - /// - /// Adds the Enable event to the local event group and sets its properties based on config - /// - void AddDisableEventToGroup() - { - if (!FeatureEventGroup.ScheduledEvents.ContainsKey(FeatureDisableEventName)) - { - CreateEvent(FeatureDisableEvent, FeatureDisableEventName); - } - else - { - FeatureDisableEvent = FeatureEventGroup.ScheduledEvents[FeatureDisableEventName]; - Debug.LogMessage(LogEventLevel.Debug, this, "Disable event already found in group"); - - // Check config times against DateAndTime of existing event. If different, delete existing event and create new event - if(!CheckExistingEventTimeForMatch(FeatureDisableEvent, FeatureDisabledTime)) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Existing event does not match new config properties. Deleting exisiting event: '{0}'", FeatureDisableEvent.Name); - - FeatureEventGroup.DeleteEvent(FeatureDisableEvent); - - FeatureDisableEvent = null; - - CreateEvent(FeatureDisableEvent, FeatureDisableEventName); - } - } - } - - - /// - /// Calculates the correct bitfield enum value for the event recurrence based on the config values - /// - /// - ScheduledEventCommon.eWeekDays CalculateDaysOfWeekRecurrence() - { - ScheduledEventCommon.eWeekDays value = new ScheduledEventCommon.eWeekDays(); - - if (PropertiesConfig.EnableSunday) - value = value | ScheduledEventCommon.eWeekDays.Sunday; - if (PropertiesConfig.EnableMonday) - value = value | ScheduledEventCommon.eWeekDays.Monday; - if (PropertiesConfig.EnableTuesday) - value = value | ScheduledEventCommon.eWeekDays.Tuesday; - if (PropertiesConfig.EnableWednesday) - value = value | ScheduledEventCommon.eWeekDays.Wednesday; - if (PropertiesConfig.EnableThursday) - value = value | ScheduledEventCommon.eWeekDays.Thursday; - if (PropertiesConfig.EnableFriday) - value = value | ScheduledEventCommon.eWeekDays.Friday; - if (PropertiesConfig.EnableSaturday) - value = value | ScheduledEventCommon.eWeekDays.Saturday; - - return value; - } - - /// - /// Callback for event that enables feature. Enables feature if config property is true - /// - /// - /// - void FeatureEnableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) - { - if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) - { - if(PropertiesConfig.EnableRoomOnWhenOccupied) - FeatureEnabled = true; - - Debug.LogMessage(LogEventLevel.Debug, this, "RoomOnToDefaultSourceWhenOccupied Feature Enabled."); - } - } - - /// - /// Callback for event that enables feature. Disables feature - /// - /// - /// - void FeatureDisableEvent_UserCallBack(ScheduledEvent SchEvent, ScheduledEventCommon.eCallbackReason type) - { - if (type == ScheduledEventCommon.eCallbackReason.NormalExpiration) - { - FeatureEnabled = false; - - Debug.LogMessage(LogEventLevel.Debug, this, "RoomOnToDefaultSourceWhenOccupied Feature Disabled."); - } - } -} - -public class RoomOnToDefaultSourceWhenOccupiedConfig -{ - [JsonProperty("roomKey")] - public string RoomKey { get; set; } - - [JsonProperty("enableRoomOnWhenOccupied")] - public bool EnableRoomOnWhenOccupied { get; set; } - - [JsonProperty("occupancyStartTime")] - public string OccupancyStartTime { get; set; } - - [JsonProperty("occupancyEndTime")] - public string OccupancyEndTime { get; set; } - - [JsonProperty("enableSunday")] - public bool EnableSunday { get; set; } - - [JsonProperty("enableMonday")] - public bool EnableMonday { get; set; } - - [JsonProperty("enableTuesday")] - public bool EnableTuesday { get; set; } - - [JsonProperty("enableWednesday")] - public bool EnableWednesday { get; set; } - - [JsonProperty("enableThursday")] - public bool EnableThursday { get; set; } - - [JsonProperty("enableFriday")] - public bool EnableFriday { get; set; } - - [JsonProperty("enableSaturday")] - public bool EnableSaturday { get; set; } -} - -public class RoomOnToDefaultSourceWhenOccupiedFactory : EssentialsDeviceFactory -{ - public RoomOnToDefaultSourceWhenOccupiedFactory() - { - TypeNames = new List() { "roomonwhenoccupancydetectedfeature" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RoomOnToDefaultSourceWhenOccupied Device"); - return new RoomOnToDefaultSourceWhenOccupied(dc); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs index 64a468db..a45adb27 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombiner.cs @@ -1,493 +1,498 @@ -using PepperDash.Core; +using Crestron.SimplSharp; +using PepperDash.Core; using PepperDash.Core.Logging; using Serilog.Events; using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Timer = System.Timers.Timer; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a device that manages room combinations by controlling partitions and scenarios. -/// -/// The allows for dynamic configuration of room -/// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes -/// for managing room combinations. In automatic mode, the device determines the current room combination scenario -/// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user. -public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner +namespace PepperDash.Essentials.Core { - private EssentialsRoomCombinerPropertiesConfig _propertiesConfig; - - private IRoomCombinationScenario _currentScenario; - - private List _rooms; - /// - /// Gets a list of rooms represented as key-name pairs. + /// Represents a device that manages room combinations by controlling partitions and scenarios. /// - public List Rooms + /// The allows for dynamic configuration of room + /// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes + /// for managing room combinations. In automatic mode, the device determines the current room combination scenario + /// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user. + public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner { - get - { - return _rooms.Cast().ToList(); - } - } + private EssentialsRoomCombinerPropertiesConfig _propertiesConfig; - private bool _isInAutoMode; + private IRoomCombinationScenario _currentScenario; - /// - /// Gets or sets a value indicating whether the system is operating in automatic mode. - /// - /// Changing this property triggers an update event via - /// IsInAutoModeFeedback.FireUpdate(). Ensure that any event listeners are properly configured to handle - /// this update. - public bool IsInAutoMode - { - get + private List _rooms; + + /// + /// Gets a list of rooms represented as key-name pairs. + /// + public List Rooms { - return _isInAutoMode; - } - set - { - if (value == _isInAutoMode) + get { - return; - } - - _isInAutoMode = value; - IsInAutoModeFeedback.FireUpdate(); - } - } - - /// - /// Gets a value indicating whether automatic mode is disabled. - /// - public bool DisableAutoMode - { - get - { - return _propertiesConfig.DisableAutoMode; - } - } - - private Timer _scenarioChangeDebounceTimer; - - private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s - - private Mutex _scenarioChange = new Mutex(); - - /// - /// Initializes a new instance of the class, which manages room combination - /// scenarios and partition states. - /// - /// The class is designed to handle dynamic room - /// combination scenarios based on partition states. It supports both automatic and manual modes for managing - /// room combinations. By default, the instance starts in automatic mode unless the - /// specifies otherwise. After activation, the room combiner initializes partition state providers and sets up - /// the initial room configuration. Additionally, it subscribes to the event to ensure proper initialization of dependent devices - /// before determining or setting the room combination scenario. - /// The unique identifier for the room combiner instance. - /// The configuration properties for the room combiner, including default settings and debounce times. - public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props) - : base(key) - { - _propertiesConfig = props; - - Partitions = new List(); - RoomCombinationScenarios = new List(); - - if (_propertiesConfig.ScenarioChangeDebounceTimeSeconds > 0) - { - _scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds; - } - - IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode); - - // default to auto mode - IsInAutoMode = true; - - if (_propertiesConfig.defaultToManualMode) - { - IsInAutoMode = false; - } - - IsInAutoModeFeedback.FireUpdate(); - - CreateScenarios(); - - AddPostActivationAction(() => - { - SetupPartitionStateProviders(); - - SetRooms(); - }); - - - // Subscribe to the AllDevicesInitialized event - // We need to wait until all devices are initialized in case - // any actions are dependent on 3rd party devices already being - // connected and initialized - DeviceManager.AllDevicesInitialized += (o, a) => - { - if (IsInAutoMode) - { - DetermineRoomCombinationScenario(); - } - else - { - SetRoomCombinationScenario(_propertiesConfig.defaultScenarioKey); - } - }; - } - - private void CreateScenarios() - { - foreach (var scenarioConfig in _propertiesConfig.Scenarios) - { - var scenario = new RoomCombinationScenario(scenarioConfig); - RoomCombinationScenarios.Add(scenario); - } - } - - private void SetRooms() - { - _rooms = new List(); - - foreach (var roomKey in _propertiesConfig.RoomKeys) - { - var room = DeviceManager.GetDeviceForKey(roomKey); - - if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom essentialsRoom) - { - _rooms.Add(essentialsRoom); + return _rooms.Cast().ToList(); } } - var rooms = DeviceManager.AllDevices.OfType().Cast(); + private bool _isInAutoMode; - foreach (var room in rooms) + /// + /// Gets or sets a value indicating whether the system is operating in automatic mode. + /// + /// Changing this property triggers an update event via + /// IsInAutoModeFeedback.FireUpdate(). Ensure that any event listeners are properly configured to handle + /// this update. + public bool IsInAutoMode { - room.Deactivate(); - } - } - - private void SetupPartitionStateProviders() - { - foreach (var pConfig in _propertiesConfig.Partitions) - { - var sensor = DeviceManager.GetDeviceForKey(pConfig.DeviceKey) as IPartitionStateProvider; - - var partition = new EssentialsPartitionController(pConfig.Key, pConfig.Name, sensor, _propertiesConfig.defaultToManualMode, pConfig.AdjacentRoomKeys); - - partition.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; - - Partitions.Add(partition); - } - } - - private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - StartDebounceTimer(); - } - - private void StartDebounceTimer() - { - // default to 500ms for manual mode - var time = 500; - - // if in auto mode, debounce the scenario change - if (IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000; - - if (_scenarioChangeDebounceTimer == null) - { - _scenarioChangeDebounceTimer = new Timer(time) { AutoReset = false }; - _scenarioChangeDebounceTimer.Elapsed += async (s, e) => await DetermineRoomCombinationScenario(); - _scenarioChangeDebounceTimer.Start(); - } - else - { - _scenarioChangeDebounceTimer.Stop(); - _scenarioChangeDebounceTimer.Interval = time; - _scenarioChangeDebounceTimer.Start(); - } - } - - /// - /// Determines the current room combination scenario based on the state of the partition sensors - /// - private async Task DetermineRoomCombinationScenario() - { - if (_scenarioChangeDebounceTimer != null) - { - _scenarioChangeDebounceTimer.Dispose(); - _scenarioChangeDebounceTimer = null; - } - - this.LogInformation("Determining Combination Scenario"); - - var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) => - { - this.LogDebug("Checking scenario {scenarioKey}", s.Key); - // iterate the partition states - foreach (var partitionState in s.PartitionStates) + get { - this.LogDebug("checking PartitionState {partitionStateKey}", partitionState.PartitionKey); - // get the partition by key - var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey)); - - this.LogDebug("Expected State: {partitionPresent} Actual State: {partitionState}", partitionState.PartitionPresent, partition.PartitionPresentFeedback.BoolValue); - - if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue) + return _isInAutoMode; + } + set + { + if (value == _isInAutoMode) { - // the partition can't be found or the state doesn't match - return false; + return; } + + _isInAutoMode = value; + IsInAutoModeFeedback.FireUpdate(); } - // if it hasn't returned false by now we have the matching scenario - return true; - }); - - if (currentScenario != null) - { - this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key); - await ChangeScenario(currentScenario); } - } - private async Task ChangeScenario(IRoomCombinationScenario newScenario) - { - - - if (newScenario == _currentScenario) + /// + /// Gets a value indicating whether automatic mode is disabled. + /// + public bool DisableAutoMode + { + get { - return; + return _propertiesConfig.DisableAutoMode; + } + } + + private CTimer _scenarioChangeDebounceTimer; + + private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s + + private Mutex _scenarioChange = new Mutex(); + + /// + /// Initializes a new instance of the class, which manages room combination + /// scenarios and partition states. + /// + /// The class is designed to handle dynamic room + /// combination scenarios based on partition states. It supports both automatic and manual modes for managing + /// room combinations. By default, the instance starts in automatic mode unless the + /// specifies otherwise. After activation, the room combiner initializes partition state providers and sets up + /// the initial room configuration. Additionally, it subscribes to the event to ensure proper initialization of dependent devices + /// before determining or setting the room combination scenario. + /// The unique identifier for the room combiner instance. + /// The configuration properties for the room combiner, including default settings and debounce times. + public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props) + : base(key) + { + _propertiesConfig = props; + + Partitions = new List(); + RoomCombinationScenarios = new List(); + + if (_propertiesConfig.ScenarioChangeDebounceTimeSeconds > 0) + { + _scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds; } - // Deactivate the old scenario first - if (_currentScenario != null) + IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode); + + // default to auto mode + IsInAutoMode = true; + + if (_propertiesConfig.defaultToManualMode) { - Debug.LogMessage(LogEventLevel.Information, "Deactivating scenario {currentScenario}", this, _currentScenario.Name); - await _currentScenario.Deactivate(); + IsInAutoMode = false; } - _currentScenario = newScenario; + IsInAutoModeFeedback.FireUpdate(); - // Activate the new scenario - if (_currentScenario != null) + CreateScenarios(); + + AddPostActivationAction(() => { - Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); - await _currentScenario.Activate(); - } + SetupPartitionStateProviders(); - RoomCombinationScenarioChanged?.Invoke(this, new EventArgs()); - - - } - - #region IEssentialsRoomCombiner Members - - /// - /// Occurs when the room combination scenario changes. - /// - /// This event is triggered whenever the configuration or state of the room combination - /// changes. Subscribers can use this event to update their logic or UI based on the new scenario. - public event EventHandler RoomCombinationScenarioChanged; - - /// - /// Gets the current room combination scenario. - /// - public IRoomCombinationScenario CurrentScenario - { - get - { - return _currentScenario; - } - } - - /// - /// Gets the feedback indicating whether the system is currently in auto mode. - /// - public BoolFeedback IsInAutoModeFeedback { get; private set; } - - /// - /// Enables auto mode for the room combiner and its partitions, allowing automatic room combination scenarios to - /// be determined. - /// - /// Auto mode allows the room combiner to automatically adjust its configuration based on - /// the state of its partitions. If auto mode is disabled in the configuration, this method logs a warning and - /// does not enable auto mode. - public void SetAutoMode() - { - if(_propertiesConfig.DisableAutoMode) - { - this.LogWarning("Auto mode is disabled for this room combiner. Cannot set to auto mode."); - return; - } - IsInAutoMode = true; - - foreach (var partition in Partitions) - { - partition.SetAutoMode(); - } - - DetermineRoomCombinationScenario(); - } - - /// - /// Switches the system to manual mode, disabling automatic operations. - /// - /// This method sets the system to manual mode by updating the mode state and propagates - /// the change to all partitions. Once in manual mode, automatic operations are disabled for the system and its - /// partitions. - public void SetManualMode() - { - IsInAutoMode = false; - - foreach (var partition in Partitions) - { - partition.SetManualMode(); - } - } - - /// - /// Toggles the current mode between automatic and manual. - /// - /// If the current mode is automatic, this method switches to manual mode. If the - /// current mode is manual, it switches to automatic mode. - public void ToggleMode() - { - if (IsInAutoMode) - { - SetManualMode(); - } - else - { - SetAutoMode(); - } - } - - /// - /// Gets the collection of room combination scenarios. - /// - public List RoomCombinationScenarios { get; private set; } - - /// - /// Gets the collection of partition controllers managed by this instance. - /// - public List Partitions { get; private set; } - - /// - /// Toggles the state of the partition identified by the specified partition key. - /// - /// If no partition with the specified key exists, the method performs no - /// action. - /// The key of the partition whose state is to be toggled. This value cannot be null or empty. - public void TogglePartitionState(string partitionKey) - { - var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey)); - - if (partition != null) - { - partition.ToggglePartitionState(); - } - } - - /// - /// Sets the room combination scenario based on the specified scenario key. - /// - /// This method manually adjusts the partition states according to the specified - /// scenario. If the application is in auto mode, the operation will not proceed, and a log message will be - /// generated indicating that the mode must be set to manual first. If the specified scenario key does not - /// match any existing scenario, a debug log message will be generated. For each partition state in the - /// scenario, the corresponding partition will be updated to either "Present" or "Not Present" based on the - /// scenario's configuration. If a partition key in the scenario cannot be found, a debug log message will be - /// generated. - /// The key identifying the room combination scenario to apply. This must match the key of an existing scenario. - public void SetRoomCombinationScenario(string scenarioKey) - { - if (IsInAutoMode) - { - Debug.LogMessage(LogEventLevel.Information, this, "Cannot set room combination scenario when in auto mode. Set to auto mode first."); - return; - } - - // Get the scenario - var scenario = RoomCombinationScenarios.FirstOrDefault((s) => s.Key.Equals(scenarioKey)); + SetRooms(); + }); - // Set the parition states from the scenario manually - if (scenario != null) - { - Debug.LogMessage(LogEventLevel.Information, this, "Manually setting scenario to '{0}'", scenario.Key); - foreach (var partitionState in scenario.PartitionStates) + // Subscribe to the AllDevicesInitialized event + // We need to wait until all devices are initialized in case + // any actions are dependent on 3rd party devices already being + // connected and initialized + DeviceManager.AllDevicesInitialized += (o, a) => { - var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey)); - - if (partition != null) + if (IsInAutoMode) { - if (partitionState.PartitionPresent) - { - Debug.LogMessage(LogEventLevel.Information, this, "Manually setting state to Present for: '{0}'", partition.Key); - partition.SetPartitionStatePresent(); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Manually setting state to Not Present for: '{0}'", partition.Key); - partition.SetPartitionStateNotPresent(); - } + DetermineRoomCombinationScenario(); } else { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to find partition with key: '{0}'", partitionState.PartitionKey); + SetRoomCombinationScenario(_propertiesConfig.defaultScenarioKey); } + }; + } + + private void CreateScenarios() + { + foreach (var scenarioConfig in _propertiesConfig.Scenarios) + { + var scenario = new RoomCombinationScenario(scenarioConfig); + RoomCombinationScenarios.Add(scenario); } } - else + + private void SetRooms() { - Debug.LogMessage(LogEventLevel.Debug, this, "Unable to find scenario with key: '{0}'", scenarioKey); + _rooms = new List(); + + foreach (var roomKey in _propertiesConfig.RoomKeys) + { + var room = DeviceManager.GetDeviceForKey(roomKey); + + if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom essentialsRoom) + { + _rooms.Add(essentialsRoom); + } + } + + var rooms = DeviceManager.AllDevices.OfType().Cast(); + + foreach (var room in rooms) + { + room.Deactivate(); + } + } + + private void SetupPartitionStateProviders() + { + foreach (var pConfig in _propertiesConfig.Partitions) + { + var sensor = DeviceManager.GetDeviceForKey(pConfig.DeviceKey) as IPartitionStateProvider; + + var partition = new EssentialsPartitionController(pConfig.Key, pConfig.Name, sensor, _propertiesConfig.defaultToManualMode, pConfig.AdjacentRoomKeys); + + partition.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange; + + Partitions.Add(partition); + } + } + + private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + StartDebounceTimer(); + } + + private void StartDebounceTimer() + { + // default to 500ms for manual mode + var time = 500; + + // if in auto mode, debounce the scenario change + if (IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000; + + if (_scenarioChangeDebounceTimer == null) + { + _scenarioChangeDebounceTimer = new CTimer((o) => DetermineRoomCombinationScenario(), time); + } + else + { + _scenarioChangeDebounceTimer.Reset(time); + } + } + + /// + /// Determines the current room combination scenario based on the state of the partition sensors + /// + private void DetermineRoomCombinationScenario() + { + if (_scenarioChangeDebounceTimer != null) + { + _scenarioChangeDebounceTimer.Dispose(); + _scenarioChangeDebounceTimer = null; + } + + this.LogInformation("Determining Combination Scenario"); + + var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) => + { + this.LogDebug("Checking scenario {scenarioKey}", s.Key); + // iterate the partition states + foreach (var partitionState in s.PartitionStates) + { + this.LogDebug("checking PartitionState {partitionStateKey}", partitionState.PartitionKey); + // get the partition by key + var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey)); + + this.LogDebug("Expected State: {partitionPresent} Actual State: {partitionState}", partitionState.PartitionPresent, partition.PartitionPresentFeedback.BoolValue); + + if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue) + { + // the partition can't be found or the state doesn't match + return false; + } + } + // if it hasn't returned false by now we have the matching scenario + return true; + }); + + if (currentScenario != null) + { + this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key); + ChangeScenario(currentScenario); + } + } + + private async Task ChangeScenario(IRoomCombinationScenario newScenario) + { + + + if (newScenario == _currentScenario) + { + return; + } + + // Deactivate the old scenario first + if (_currentScenario != null) + { + Debug.LogMessage(LogEventLevel.Information, "Deactivating scenario {currentScenario}", this, _currentScenario.Name); + await _currentScenario.Deactivate(); + } + + _currentScenario = newScenario; + + // Activate the new scenario + if (_currentScenario != null) + { + Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this); + await _currentScenario.Activate(); + } + + RoomCombinationScenarioChanged?.Invoke(this, new EventArgs()); + + + } + + #region IEssentialsRoomCombiner Members + + /// + /// Occurs when the room combination scenario changes. + /// + /// This event is triggered whenever the configuration or state of the room combination + /// changes. Subscribers can use this event to update their logic or UI based on the new scenario. + public event EventHandler RoomCombinationScenarioChanged; + + /// + /// Gets the current room combination scenario. + /// + public IRoomCombinationScenario CurrentScenario + { + get + { + return _currentScenario; + } + } + + /// + /// Gets or sets the IsInAutoModeFeedback + /// + public BoolFeedback IsInAutoModeFeedback { get; private set; } + + /// + /// Enables auto mode for the room combiner and its partitions, allowing automatic room combination scenarios to + /// be determined. + /// + /// Auto mode allows the room combiner to automatically adjust its configuration based on + /// the state of its partitions. If auto mode is disabled in the configuration, this method logs a warning and + /// does not enable auto mode. + public void SetAutoMode() + { + if(_propertiesConfig.DisableAutoMode) + { + this.LogWarning("Auto mode is disabled for this room combiner. Cannot set to auto mode."); + return; + } + IsInAutoMode = true; + + foreach (var partition in Partitions) + { + partition.SetAutoMode(); + } + + DetermineRoomCombinationScenario(); + } + + /// + /// Switches the system to manual mode, disabling automatic operations. + /// + /// This method sets the system to manual mode by updating the mode state and propagates + /// the change to all partitions. Once in manual mode, automatic operations are disabled for the system and its + /// partitions. + /// + /// SetManualMode method + /// + public void SetManualMode() + { + IsInAutoMode = false; + + foreach (var partition in Partitions) + { + partition.SetManualMode(); + } + } + + /// + /// Toggles the current mode between automatic and manual. + /// + /// If the current mode is automatic, this method switches to manual mode. If the + /// current mode is manual, it switches to automatic mode. + /// + /// ToggleMode method + /// + public void ToggleMode() + { + if (IsInAutoMode) + { + SetManualMode(); + } + else + { + SetAutoMode(); + } + } + + /// + /// Gets or sets the RoomCombinationScenarios + /// + public List RoomCombinationScenarios { get; private set; } + + /// + /// Gets the collection of partition controllers managed by this instance. + /// + public List Partitions { get; private set; } + + /// + /// Toggles the state of the partition identified by the specified partition key. + /// + /// If no partition with the specified key exists, the method performs no + /// action. + /// The key of the partition whose state is to be toggled. This value cannot be null or empty. + public void TogglePartitionState(string partitionKey) + { + var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey)); + + if (partition != null) + { + partition.ToggglePartitionState(); + } + } + + /// + /// Sets the room combination scenario based on the specified scenario key. + /// + /// This method manually adjusts the partition states according to the specified + /// scenario. If the application is in auto mode, the operation will not proceed, and a log message will be + /// generated indicating that the mode must be set to manual first. If the specified scenario key does not + /// match any existing scenario, a debug log message will be generated. For each partition state in the + /// scenario, the corresponding partition will be updated to either "Present" or "Not Present" based on the + /// scenario's configuration. If a partition key in the scenario cannot be found, a debug log message will be + /// generated. + /// The key identifying the room combination scenario to apply. This must match the key of an existing scenario. + public void SetRoomCombinationScenario(string scenarioKey) + { + if (IsInAutoMode) + { + Debug.LogMessage(LogEventLevel.Information, this, "Cannot set room combination scenario when in auto mode. Set to auto mode first."); + return; + } + + // Get the scenario + var scenario = RoomCombinationScenarios.FirstOrDefault((s) => s.Key.Equals(scenarioKey)); + + + // Set the parition states from the scenario manually + if (scenario != null) + { + Debug.LogMessage(LogEventLevel.Information, this, "Manually setting scenario to '{0}'", scenario.Key); + foreach (var partitionState in scenario.PartitionStates) + { + var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey)); + + if (partition != null) + { + if (partitionState.PartitionPresent) + { + Debug.LogMessage(LogEventLevel.Information, this, "Manually setting state to Present for: '{0}'", partition.Key); + partition.SetPartitionStatePresent(); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Manually setting state to Not Present for: '{0}'", partition.Key); + partition.SetPartitionStateNotPresent(); + } + } + else + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to find partition with key: '{0}'", partitionState.PartitionKey); + } + } + } + else + { + Debug.LogMessage(LogEventLevel.Debug, this, "Unable to find scenario with key: '{0}'", scenarioKey); + } + } + + #endregion + } + + /// + /// Provides a factory for creating instances of devices. + /// + /// This factory is responsible for constructing devices + /// based on the provided configuration. It supports the type name "essentialsroomcombiner" for device + /// creation. + /// + /// Represents a EssentialsRoomCombinerFactory + /// + public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory + { + /// + /// Initializes a new instance of the class. + /// + /// This factory is used to create instances of room combiners with the specified type + /// names. By default, the factory includes the type name "essentialsroomcombiner". + public EssentialsRoomCombinerFactory() + { + TypeNames = new List { "essentialsroomcombiner" }; + } + + /// + /// Creates and initializes a new instance of the device. + /// + /// This method uses the provided device configuration to extract the properties and + /// create an device. Ensure that the configuration contains valid + /// properties for the device to be created successfully. + /// The device configuration containing the key and properties required to build the device. + /// A new instance of initialized with the specified configuration. + public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EssentialsRoomCombiner Device"); + + var props = dc.Properties.ToObject(); + + return new EssentialsRoomCombiner(dc.Key, props); } } - - #endregion -} - -/// -/// Provides a factory for creating instances of devices. -/// -/// This factory is responsible for constructing devices -/// based on the provided configuration. It supports the type name "essentialsroomcombiner" for device -/// creation. -public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the class. - /// - /// This factory is used to create instances of room combiners with the specified type - /// names. By default, the factory includes the type name "essentialsroomcombiner". - public EssentialsRoomCombinerFactory() - { - TypeNames = new List { "essentialsroomcombiner" }; - } - - /// - /// Creates and initializes a new instance of the device. - /// - /// This method uses the provided device configuration to extract the properties and - /// create an device. Ensure that the configuration contains valid - /// properties for the device to be created successfully. - /// The device configuration containing the key and properties required to build the device. - /// A new instance of initialized with the specified configuration. - public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EssentialsRoomCombiner Device"); - - var props = dc.Properties.ToObject(); - - return new EssentialsRoomCombiner(dc.Key, props); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombinerPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombinerPropertiesConfig.cs index a8e9ae97..868f3992 100644 --- a/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombinerPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Combining/EssentialsRoomCombinerPropertiesConfig.cs @@ -1,4 +1,3 @@ - using System.Collections.Generic; using PepperDash.Core; @@ -12,10 +11,10 @@ namespace PepperDash.Essentials.Core /// public class EssentialsRoomCombinerPropertiesConfig { - /// - /// Gets or sets a value indicating whether the system operates in automatic mode. - /// Some systems don't have partitions sensors, and show shouldn't allow auto mode to be turned on. When this is true in the configuration, - /// auto mode won't be allowed to be turned on. + /// + /// Gets or sets a value indicating whether the system operates in automatic mode. + /// Some systems don't have partitions sensors, and show shouldn't allow auto mode to be turned on. When this is true in the configuration, + /// auto mode won't be allowed to be turned on. /// [JsonProperty("disableAutoMode")] public bool DisableAutoMode { get; set; } @@ -50,8 +49,8 @@ namespace PepperDash.Essentials.Core [JsonProperty("defaultScenarioKey")] public string defaultScenarioKey { get; set; } - /// - /// Gets or sets the debounce time, in seconds, for scenario changes. + /// + /// Gets or sets the debounce time, in seconds, for scenario changes. /// [JsonProperty("scenarioChangeDebounceTimeSeconds")] public int ScenarioChangeDebounceTimeSeconds { get; set; } @@ -62,14 +61,14 @@ namespace PepperDash.Essentials.Core /// public class PartitionConfig : IKeyName { - /// - /// Gets or sets the unique key associated with the object. + /// + /// Gets or sets the unique key associated with the object. /// [JsonProperty("key")] public string Key { get; set; } - /// - /// Gets or sets the name associated with the object. + /// + /// Gets or sets the name associated with the object. /// [JsonProperty("name")] public string Name { get; set; } @@ -92,26 +91,26 @@ namespace PepperDash.Essentials.Core /// public class RoomCombinationScenarioConfig : IKeyName { - /// - /// Gets or sets the key associated with the object. + /// + /// Gets or sets the key associated with the object. /// [JsonProperty("key")] public string Key { get; set; } - /// - /// Gets or sets the name associated with the object. + /// + /// Gets or sets the name associated with the object. /// [JsonProperty("name")] public string Name { get; set; } - /// - /// Gets or sets a value indicating whether to hide this scenario in the UI. + /// + /// Gets or sets a value indicating whether to hide this scenario in the UI. /// [JsonProperty("hideInUi", NullValueHandling = NullValueHandling.Ignore)] - public bool HideInUi { get; set; } - - /// - /// Gets or sets the collection of partition states. + public bool HideInUi { get; set; } + + /// + /// Gets or sets the collection of partition states. /// [JsonProperty("partitionStates")] public List PartitionStates { get; set; } @@ -122,14 +121,14 @@ namespace PepperDash.Essentials.Core [JsonProperty("uiMap")] public Dictionary UiMap { get; set; } - /// - /// Gets or sets the list of actions to be performed during device activation. + /// + /// Gets or sets the list of actions to be performed during device activation. /// [JsonProperty("activationActions")] public List ActivationActions { get; set; } - /// - /// Gets or sets the list of actions to be performed when a device is deactivated. + /// + /// Gets or sets the list of actions to be performed when a device is deactivated. /// [JsonProperty("deactivationActions")] public List DeactivationActions { get; set; } @@ -140,16 +139,16 @@ namespace PepperDash.Essentials.Core /// public class PartitionState { - /// - /// Gets or sets the partition key used to group and organize data within a storage system. + /// + /// Gets or sets the partition key used to group and organize data within a storage system. /// [JsonProperty("partitionKey")] public string PartitionKey { get; set; } - /// - /// Gets or sets a value indicating whether a partition is currently present. + /// + /// Gets or sets a value indicating whether a partition is currently present. /// [JsonProperty("partitionSensedState")] public bool PartitionPresent { get; set; } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs index 6bda3ac9..83584a5f 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsDualDisplayRoomPropertiesConfig.cs @@ -1,10 +1,11 @@  -namespace PepperDash.Essentials.Room.Config; - -/// -/// Represents the configuration properties for a dual display room. -/// -public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig +namespace PepperDash.Essentials.Room.Config { + /// + /// Represents a EssentialsDualDisplayRoomPropertiesConfig + /// + public class EssentialsDualDisplayRoomPropertiesConfig : EssentialsNDisplayRoomPropertiesConfig + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs index abd2bce6..85f8ca16 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleRoomPropertiesConfig.cs @@ -1,33 +1,34 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials.Room.Config; - -/// -/// Configuration class for huddle room properties -/// -public class EssentialsHuddleRoomPropertiesConfig : EssentialsRoomPropertiesConfig +namespace PepperDash.Essentials.Room.Config { /// - /// The key of the default display device + /// Represents a EssentialsHuddleRoomPropertiesConfig /// - [JsonProperty("defaultDisplayKey")] - public string DefaultDisplayKey { get; set; } + public class EssentialsHuddleRoomPropertiesConfig : EssentialsRoomPropertiesConfig + { + /// + /// The key of the default display device + /// + [JsonProperty("defaultDisplayKey")] + public string DefaultDisplayKey { get; set; } - /// - /// The key of the default audio device - /// - [JsonProperty("defaultAudioKey")] - public string DefaultAudioKey { get; set; } + /// + /// The key of the default audio device + /// + [JsonProperty("defaultAudioKey")] + public string DefaultAudioKey { get; set; } - /// - /// The key of the source list for the room - /// - [JsonProperty("sourceListKey")] - public string SourceListKey { get; set; } + /// + /// The key of the source list for the room + /// + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } - /// - /// The key of the default source item from the source list - /// - [JsonProperty("defaultSourceItem")] - public string DefaultSourceItem { get; set; } + /// + /// The key of the default source item from the source list + /// + [JsonProperty("defaultSourceItem")] + public string DefaultSourceItem { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs index 94de1d46..c41ed3c2 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsHuddleVtc1PropertiesConfig.cs @@ -1,16 +1,19 @@  using Newtonsoft.Json; -namespace PepperDash.Essentials.Room.Config; - -/// -/// Represents a EssentialsHuddleVtc1PropertiesConfig -/// -public class EssentialsHuddleVtc1PropertiesConfig : EssentialsConferenceRoomPropertiesConfig +namespace PepperDash.Essentials.Room.Config { + /// - /// Gets or sets the DefaultDisplayKey + /// Represents a EssentialsHuddleVtc1PropertiesConfig /// - [JsonProperty("defaultDisplayKey")] - public string DefaultDisplayKey { get; set; } -} + public class EssentialsHuddleVtc1PropertiesConfig : EssentialsConferenceRoomPropertiesConfig + { + /// + /// Gets or sets the DefaultDisplayKey + /// + [JsonProperty("defaultDisplayKey")] + public string DefaultDisplayKey { get; set; } + + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs index 85dcc6ad..f65864b8 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsNDisplayRoomPropertiesConfig.cs @@ -4,49 +4,55 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Room.Config; - -/// -/// -/// -public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPropertiesConfig +namespace PepperDash.Essentials.Room.Config { - /// - /// Gets or sets the DefaultAudioBehavior + /// /// - [JsonProperty("defaultAudioBehavior")] - public string DefaultAudioBehavior { get; set; } - - /// - /// Gets or sets the DefaultVideoBehavior - /// - [JsonProperty("defaultVideoBehavior")] - public string DefaultVideoBehavior { get; set; } - - - /// - /// Constructor - /// - public EssentialsNDisplayRoomPropertiesConfig() + public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPropertiesConfig { + /// + /// Gets or sets the DefaultAudioBehavior + /// + [JsonProperty("defaultAudioBehavior")] + public string DefaultAudioBehavior { get; set; } + + /// + /// Gets or sets the DefaultVideoBehavior + /// + [JsonProperty("defaultVideoBehavior")] + public string DefaultVideoBehavior { get; set; } + + /// + /// Gets or sets the Displays + /// + [JsonProperty("displays")] + public Dictionary Displays { get; set; } + + /// + /// Constructor + /// + public EssentialsNDisplayRoomPropertiesConfig() + { + Displays = new Dictionary(); + } + } -} - -/// -/// Represents a DisplayItem -/// -public class DisplayItem : IKeyName -{ /// - /// Gets or sets the Key + /// Represents a DisplayItem /// - public string Key { get; set; } - - /// - /// Gets or sets the Name - /// - public string Name { get; set; } -} + public class DisplayItem : IKeyName + { + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs index 9f563489..7de2405a 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomConfig.cs @@ -7,71 +7,70 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Privacy; using Serilog.Events; -namespace PepperDash.Essentials.Room.Config; - -/// -/// Helper class for EssentialsRoomConfig -/// -public class EssentialsRoomConfigHelper +namespace PepperDash.Essentials.Room.Config { /// - /// Gets and operating, standalone emergegncy object that can be plugged into a room. - /// Returns null if there is no emergency defined + /// Represents a EssentialsRoomConfigHelper /// - public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, IEssentialsRoom room) + public class EssentialsRoomConfigHelper { - // This emergency - var emergency = props.Emergency; - if (emergency != null) + /// + /// GetEmergency method + /// + public static EssentialsRoomEmergencyBase GetEmergency(EssentialsRoomPropertiesConfig props, IEssentialsRoom room) { - //switch on emergency type here. Right now only contact and shutdown - var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); - DeviceManager.AddDevice(e); - return e; - } - return null; - } - - /// - /// - /// - /// - /// - /// - /// - /// GetMicrophonePrivacy method - /// - public static MicrophonePrivacyController GetMicrophonePrivacy( - EssentialsRoomPropertiesConfig props, IPrivacy room) - { - var microphonePrivacy = props.MicrophonePrivacy; - if (microphonePrivacy == null) - { - Debug.LogMessage(LogEventLevel.Information, "Cannot create microphone privacy with null properties"); + // This emergency + var emergency = props.Emergency; + if (emergency != null) + { + //switch on emergency type here. Right now only contact and shutdown + var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room); + DeviceManager.AddDevice(e); + return e; + } return null; } - // Get the MicrophonePrivacy device from the device manager - var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as MicrophonePrivacyController); - // Set this room as the IPrivacy device - if (mP == null) - { - Debug.LogMessage(LogEventLevel.Information, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey); - return null; - } - mP.SetPrivacyDevice(room); - var behaviour = props.MicrophonePrivacy.Behaviour.ToLower(); + /// + /// + /// + /// + /// + /// + /// + /// GetMicrophonePrivacy method + /// + public static MicrophonePrivacyController GetMicrophonePrivacy( + EssentialsRoomPropertiesConfig props, IPrivacy room) + { + var microphonePrivacy = props.MicrophonePrivacy; + if (microphonePrivacy == null) + { + Debug.LogMessage(LogEventLevel.Information, "Cannot create microphone privacy with null properties"); + return null; + } + // Get the MicrophonePrivacy device from the device manager + var mP = (DeviceManager.GetDeviceForKey(props.MicrophonePrivacy.DeviceKey) as MicrophonePrivacyController); + // Set this room as the IPrivacy device + if (mP == null) + { + Debug.LogMessage(LogEventLevel.Information, "ERROR: Selected device {0} is not MicrophonePrivacyController", props.MicrophonePrivacy.DeviceKey); + return null; + } + mP.SetPrivacyDevice(room); - if (behaviour == null) - { - Debug.LogMessage(LogEventLevel.Information, "WARNING: No behaviour defined for MicrophonePrivacyController"); - return null; - } - if (behaviour == "trackroomstate") - { - // Tie LED enable to room power state - var essRoom = room as IEssentialsRoom; - essRoom.OnFeedback.OutputChange += (o, a) => + var behaviour = props.MicrophonePrivacy.Behaviour.ToLower(); + + if (behaviour == null) + { + Debug.LogMessage(LogEventLevel.Information, "WARNING: No behaviour defined for MicrophonePrivacyController"); + return null; + } + if (behaviour == "trackroomstate") + { + // Tie LED enable to room power state + var essRoom = room as IEssentialsRoom; + essRoom.OnFeedback.OutputChange += (o, a) => { if (essRoom.OnFeedback.BoolValue) mP.EnableLeds = true; @@ -79,13 +78,13 @@ public class EssentialsRoomConfigHelper mP.EnableLeds = false; }; - mP.EnableLeds = essRoom.OnFeedback.BoolValue; - } - else if (behaviour == "trackcallstate") - { - // Tie LED enable to room power state - var inCallRoom = room as IHasInCallFeedback; - inCallRoom.InCallFeedback.OutputChange += (o, a) => + mP.EnableLeds = essRoom.OnFeedback.BoolValue; + } + else if (behaviour == "trackcallstate") + { + // Tie LED enable to room power state + var inCallRoom = room as IHasInCallFeedback; + inCallRoom.InCallFeedback.OutputChange += (o, a) => { if (inCallRoom.InCallFeedback.BoolValue) mP.EnableLeds = true; @@ -93,335 +92,477 @@ public class EssentialsRoomConfigHelper mP.EnableLeds = false; }; - mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue; + mP.EnableLeds = inCallRoom.InCallFeedback.BoolValue; + } + + return mP; } - return mP; } -} - -/// -/// -/// -public class EssentialsRoomPropertiesConfig -{ - [JsonProperty("addresses")] - public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } - /// - /// Gets or sets the Description + /// Represents a EssentialsRoomPropertiesConfig /// - [JsonProperty("description")] - public string Description { get; set; } - - /// - /// Gets or sets the Emergency - /// - [JsonProperty("emergency")] - public EssentialsRoomEmergencyConfig Emergency { get; set; } - - /// - /// Gets or sets the Help - /// - [JsonProperty("help")] - public EssentialsHelpPropertiesConfig Help { get; set; } - - /// - /// Gets or sets the HelpMessage - /// - [JsonProperty("helpMessage")] - public string HelpMessage { get; set; } - - /// - /// Read this value to get the help message. It checks for the old and new config format. - /// - public string HelpMessageForDisplay + public class EssentialsRoomPropertiesConfig { - get + /// + /// Gets or sets the Addresses + /// + [JsonProperty("addresses")] + public EssentialsRoomAddressPropertiesConfig Addresses { get; set; } + + /// + /// Gets or sets the Description + /// + [JsonProperty("description")] + public string Description { get; set; } + + /// + /// Gets or sets the Emergency + /// + [JsonProperty("emergency")] + public EssentialsRoomEmergencyConfig Emergency { get; set; } + + /// + /// Gets or sets the Help + /// + [JsonProperty("help")] + public EssentialsHelpPropertiesConfig Help { get; set; } + + /// + /// Gets or sets the HelpMessage + /// + [JsonProperty("helpMessage")] + public string HelpMessage { get; set; } + + /// + /// Read this value to get the help message. It checks for the old and new config format. + /// + public string HelpMessageForDisplay { - if (Help != null && !string.IsNullOrEmpty(Help.Message)) + get { - return Help.Message; - } - else - { - return HelpMessage; + if (Help != null && !string.IsNullOrEmpty(Help.Message)) + { + return Help.Message; + } + else + { + return HelpMessage; + } } } - } - /// - /// Gets or sets the Environment - /// - [JsonProperty("environment")] - public EssentialsEnvironmentPropertiesConfig Environment { get; set; } + /// + /// Gets or sets the Environment + /// + [JsonProperty("environment")] + public EssentialsEnvironmentPropertiesConfig Environment { get; set; } - /// - /// Gets or sets the LogoLight - /// - [JsonProperty("logo")] - public EssentialsLogoPropertiesConfig LogoLight { get; set; } + /// + /// Gets or sets the LogoLight + /// + [JsonProperty("logo")] + public EssentialsLogoPropertiesConfig LogoLight { get; set; } - /// - /// Gets or sets the LogoDark - /// - [JsonProperty("logoDark")] - public EssentialsLogoPropertiesConfig LogoDark { get; set; } + /// + /// Gets or sets the LogoDark + /// + [JsonProperty("logoDark")] + public EssentialsLogoPropertiesConfig LogoDark { get; set; } - /// - /// Gets or sets the MicrophonePrivacy - /// - [JsonProperty("microphonePrivacy")] - public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } + /// + /// Gets or sets the MicrophonePrivacy + /// + [JsonProperty("microphonePrivacy")] + public EssentialsRoomMicrophonePrivacyConfig MicrophonePrivacy { get; set; } - /// - /// Gets or sets the Occupancy - /// - [JsonProperty("occupancy")] - public EssentialsRoomOccSensorConfig Occupancy { get; set; } + /// + /// Gets or sets the Occupancy + /// + [JsonProperty("occupancy")] + public EssentialsRoomOccSensorConfig Occupancy { get; set; } - /// - /// Gets or sets the OneButtonMeeting - /// - [JsonProperty("oneButtonMeeting")] - public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } + /// + /// Gets or sets the OneButtonMeeting + /// + [JsonProperty("oneButtonMeeting")] + public EssentialsOneButtonMeetingPropertiesConfig OneButtonMeeting { get; set; } - /// - /// Gets or sets the ShutdownVacancySeconds - /// - [JsonProperty("shutdownVacancySeconds")] - public int ShutdownVacancySeconds { get; set; } + /// + /// Gets or sets the ShutdownVacancySeconds + /// + [JsonProperty("shutdownVacancySeconds")] + public int ShutdownVacancySeconds { get; set; } - /// - /// Gets or sets the ShutdownPromptSeconds - /// - [JsonProperty("shutdownPromptSeconds")] - public int ShutdownPromptSeconds { get; set; } + /// + /// Gets or sets the ShutdownPromptSeconds + /// + [JsonProperty("shutdownPromptSeconds")] + public int ShutdownPromptSeconds { get; set; } - /// - /// Gets or sets the Tech - /// - [JsonProperty("tech")] - public EssentialsRoomTechConfig Tech { get; set; } + /// + /// Gets or sets the Tech + /// + [JsonProperty("tech")] + public EssentialsRoomTechConfig Tech { get; set; } - [JsonProperty("fusion")] - public EssentialsRoomFusionConfig Fusion { get; set; } + /// + /// Gets or sets the Volumes + /// + [JsonProperty("volumes")] + public EssentialsRoomVolumesConfig Volumes { get; set; } - [JsonProperty("essentialsRoomUiBehaviorConfig", NullValueHandling = NullValueHandling.Ignore)] - public EssentialsRoomUiBehaviorConfig UiBehavior { get; set; } + /// + /// Gets or sets the Fusion + /// + [JsonProperty("fusion")] + public EssentialsRoomFusionConfig Fusion { get; set; } - /// - /// Gets or sets the ZeroVolumeWhenSwtichingVolumeDevices - /// - [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] - public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } + /// + /// Gets or sets the UiBehavior + /// + [JsonProperty("essentialsRoomUiBehaviorConfig", NullValueHandling = NullValueHandling.Ignore)] + public EssentialsRoomUiBehaviorConfig UiBehavior { get; set; } - /// - /// Indicates if this room represents a combination of other rooms - /// - [JsonProperty("isRoomCombinationScenario")] - public bool IsRoomCombinationScenario { get; set; } + /// + /// Gets or sets the ZeroVolumeWhenSwtichingVolumeDevices + /// + [JsonProperty("zeroVolumeWhenSwtichingVolumeDevices")] + public bool ZeroVolumeWhenSwtichingVolumeDevices { get; set; } - public EssentialsRoomPropertiesConfig() - { - LogoLight = new EssentialsLogoPropertiesConfig(); - LogoDark = new EssentialsLogoPropertiesConfig(); - } -} + /// + /// Indicates if this room represents a combination of other rooms + /// + [JsonProperty("isRoomCombinationScenario")] + public bool IsRoomCombinationScenario { get; set; } -public class EssentialsRoomUiBehaviorConfig -{ - [JsonProperty("disableActivityButtonsWhileWarmingCooling")] - public bool DisableActivityButtonsWhileWarmingCooling { get; set; } -} - -public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig -{ - [JsonProperty("defaultAudioKey")] - public string DefaultAudioKey { get; set; } - [JsonProperty("sourceListKey")] - public string SourceListKey { get; set; } - [JsonProperty("destinationListKey")] - public string DestinationListKey { get; set; } - [JsonProperty("audioControlPointListKey")] - public string AudioControlPointListKey { get; set; } - [JsonProperty("cameraListKey")] - public string CameraListKey { get; set; } - - - [JsonProperty("defaultSourceItem")] - public string DefaultSourceItem { get; set; } - /// - /// Indicates if the room supports advanced sharing - /// - [JsonProperty("supportsAdvancedSharing")] - public bool SupportsAdvancedSharing { get; set; } - /// - /// Indicates if non-tech users can change the share mode - /// - [JsonProperty("userCanChangeShareMode")] - public bool UserCanChangeShareMode { get; set; } - - - [JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)] - public string MatrixRoutingKey { get; set; } -} - -public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig -{ - [JsonProperty("videoCodecKey")] - public string VideoCodecKey { get; set; } - [JsonProperty("audioCodecKey")] - public string AudioCodecKey { get; set; } - -} - -/// -/// Represents a EssentialsEnvironmentPropertiesConfig -/// -public class EssentialsEnvironmentPropertiesConfig -{ - /// - /// Gets or sets the Enabled - /// - public bool Enabled { get; set; } - - [JsonProperty("deviceKeys")] - public List DeviceKeys { get; set; } - - public EssentialsEnvironmentPropertiesConfig() - { - DeviceKeys = new List(); - } - -} - -public class EssentialsRoomFusionConfig -{ - public uint IpIdInt - { - get + /// + /// Constructor + /// + public EssentialsRoomPropertiesConfig() { - try - { - return Convert.ToUInt32(IpId, 16); - } - catch (Exception) - { - throw new FormatException(string.Format("ERROR:Unable to convert IP ID: {0} to hex. Error:\n{1}", IpId)); - } - + LogoLight = new EssentialsLogoPropertiesConfig(); + LogoDark = new EssentialsLogoPropertiesConfig(); } } - [JsonProperty("ipId")] - public string IpId { get; set; } - - [JsonProperty("joinMapKey")] - public string JoinMapKey { get; set; } - -} - -public class EssentialsRoomMicrophonePrivacyConfig -{ - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } - - [JsonProperty("behaviour")] - public string Behaviour { get; set; } -} - -/// -/// Properties for the help text box -/// -public class EssentialsHelpPropertiesConfig -{ - [JsonProperty("message")] - public string Message { get; set; } - - [JsonProperty("showCallButton")] - public bool ShowCallButton { get; set; } + /// + /// Represents a EssentialsRoomUiBehaviorConfig + /// + public class EssentialsRoomUiBehaviorConfig + { + /// + /// Gets or sets the DisableActivityButtonsWhileWarmingCooling + /// + [JsonProperty("disableActivityButtonsWhileWarmingCooling")] + public bool DisableActivityButtonsWhileWarmingCooling { get; set; } + } /// - /// Defaults to "Call Help Desk" + /// Represents a EssentialsAvRoomPropertiesConfig /// - [JsonProperty("callButtonText")] - public string CallButtonText { get; set; } - - public EssentialsHelpPropertiesConfig() + public class EssentialsAvRoomPropertiesConfig : EssentialsRoomPropertiesConfig { - CallButtonText = "Call Help Desk"; + /// + /// Gets or sets the DefaultAudioKey + /// + [JsonProperty("defaultAudioKey")] + public string DefaultAudioKey { get; set; } + + /// + /// Gets or sets the DefaultOnDspPresetKey + /// + [JsonProperty("defaultOnDspPresetKey")] + public string DefaultOnDspPresetKey { get; set; } + + /// + /// Gets or sets the DefaultOffDspPresetKey + /// + [JsonProperty("defaultOffDspPresetKey")] + public string DefaultOffDspPresetKey { get; set; } + + /// + /// Gets or sets the SourceListKey + /// + [JsonProperty("sourceListKey")] + public string SourceListKey { get; set; } + /// + /// Gets or sets the DestinationListKey + /// + [JsonProperty("destinationListKey")] + public string DestinationListKey { get; set; } + /// + /// Gets or sets the AudioControlPointListKey + /// + [JsonProperty("audioControlPointListKey")] + public string AudioControlPointListKey { get; set; } + /// + /// Gets or sets the CameraListKey + /// + [JsonProperty("cameraListKey")] + public string CameraListKey { get; set; } + + + /// + /// Gets or sets the DefaultSourceItem + /// + [JsonProperty("defaultSourceItem")] + public string DefaultSourceItem { get; set; } + /// + /// Indicates if the room supports advanced sharing + /// + [JsonProperty("supportsAdvancedSharing")] + public bool SupportsAdvancedSharing { get; set; } + + /// + /// Indicates if non-tech users can change the share mode + /// + [JsonProperty("userCanChangeShareMode")] + public bool UserCanChangeShareMode { get; set; } + + + /// + /// Gets or sets the MatrixRoutingKey + /// + [JsonProperty("matrixRoutingKey", NullValueHandling = NullValueHandling.Ignore)] + public string MatrixRoutingKey { get; set; } } -} -/// -/// -/// -public class EssentialsOneButtonMeetingPropertiesConfig -{ - [JsonProperty("enable")] - public bool Enable { get; set; } -} - -public class EssentialsRoomAddressPropertiesConfig -{ - [JsonProperty("phoneNumber")] - public string PhoneNumber { get; set; } - - [JsonProperty("sipAddress")] - public string SipAddress { get; set; } -} - - -/// -/// Properties for the room's logo on panels -/// -public class EssentialsLogoPropertiesConfig -{ - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } /// - /// Gets either the custom URL, a local-to-processor URL, or null if it's a default logo + /// Represents a EssentialsConferenceRoomPropertiesConfig /// - public string GetLogoUrlLight() + public class EssentialsConferenceRoomPropertiesConfig : EssentialsAvRoomPropertiesConfig { - if (Type == "url") - return Url; - if (Type == "system") - return string.Format("http://{0}:8080/logo.png", - CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); - return null; + /// + /// Gets or sets the VideoCodecKey + /// + [JsonProperty("videoCodecKey")] + public string VideoCodecKey { get; set; } + /// + /// Gets or sets the AudioCodecKey + /// + [JsonProperty("audioCodecKey")] + public string AudioCodecKey { get; set; } + } - public string GetLogoUrlDark() + /// + /// Represents a EssentialsEnvironmentPropertiesConfig + /// + public class EssentialsEnvironmentPropertiesConfig { - if (Type == "url") - return Url; - if (Type == "system") - return string.Format("http://{0}:8080/logo-dark.png", - CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); - return null; + /// + /// Gets or sets the Enabled + /// + public bool Enabled { get; set; } + + /// + /// Gets or sets the DeviceKeys + /// + [JsonProperty("deviceKeys")] + public List DeviceKeys { get; set; } + + /// + /// Constructor + /// + public EssentialsEnvironmentPropertiesConfig() + { + DeviceKeys = new List(); + } + } -} -/// -/// Represents occupancy sensor(s) setup for a room -/// -public class EssentialsRoomOccSensorConfig -{ - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } + /// + /// Represents a EssentialsRoomFusionConfig + /// + public class EssentialsRoomFusionConfig + { + /// + /// Gets the the IpId as a UInt16 + /// + public uint IpIdInt + { + get + { + try + { + return Convert.ToUInt32(IpId, 16); + } + catch (Exception) + { + throw new FormatException(string.Format("ERROR:Unable to convert IP ID: {0} to hex. Error:\n{1}", IpId)); + } - [JsonProperty("timeoutMinutes")] - public int TimeoutMinutes { get; set; } -} + } + } -public class EssentialsRoomTechConfig -{ - [JsonProperty("password")] - public string Password { get; set; } -} + /// + /// Gets or sets the IpId + /// + [JsonProperty("ipId")] + public string IpId { get; set; } + + /// + /// Gets or sets the JoinMapKey + /// + [JsonProperty("joinMapKey")] + public string JoinMapKey { get; set; } + + } + + /// + /// Represents a EssentialsRoomMicrophonePrivacyConfig + /// + public class EssentialsRoomMicrophonePrivacyConfig + { + /// + /// Gets or sets the DeviceKey + /// + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + /// + /// Gets or sets the Behaviour + /// + [JsonProperty("behaviour")] + public string Behaviour { get; set; } + } + + /// + /// Represents a EssentialsHelpPropertiesConfig + /// + public class EssentialsHelpPropertiesConfig + { + /// + /// Gets or sets the Message + /// + [JsonProperty("message")] + public string Message { get; set; } + + /// + /// Gets or sets the ShowCallButton + /// + [JsonProperty("showCallButton")] + public bool ShowCallButton { get; set; } + + /// + /// Defaults to "Call Help Desk" + /// + [JsonProperty("callButtonText")] + public string CallButtonText { get; set; } + + /// + /// Constructor + /// + public EssentialsHelpPropertiesConfig() + { + CallButtonText = "Call Help Desk"; + } + } + + /// + /// Represents a EssentialsOneButtonMeetingPropertiesConfig + /// + public class EssentialsOneButtonMeetingPropertiesConfig + { + /// + /// Gets or sets the Enable + /// + [JsonProperty("enable")] + public bool Enable { get; set; } + } + + /// + /// Represents a EssentialsRoomAddressPropertiesConfig + /// + public class EssentialsRoomAddressPropertiesConfig + { + /// + /// Gets or sets the PhoneNumber + /// + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + + /// + /// Gets or sets the SipAddress + /// + [JsonProperty("sipAddress")] + public string SipAddress { get; set; } + } + + + /// + /// Represents a EssentialsLogoPropertiesConfig + /// + public class EssentialsLogoPropertiesConfig + { + /// + /// Gets or sets the Type + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// Gets or sets the Url + /// + [JsonProperty("url")] + public string Url { get; set; } + + /// + /// GetLogoUrlLight method + /// + public string GetLogoUrlLight() + { + if (Type == "url") + return Url; + if (Type == "system") + return string.Format("http://{0}:8080/logo.png", + CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + return null; + } + + /// + /// GetLogoUrlDark method + /// + public string GetLogoUrlDark() + { + if (Type == "url") + return Url; + if (Type == "system") + return string.Format("http://{0}:8080/logo-dark.png", + CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)); + return null; + } + } + + /// + /// Represents a EssentialsRoomOccSensorConfig + /// + public class EssentialsRoomOccSensorConfig + { + /// + /// Gets or sets the DeviceKey + /// + [JsonProperty("deviceKey")] + public string DeviceKey { get; set; } + + /// + /// Gets or sets the TimeoutMinutes + /// + [JsonProperty("timeoutMinutes")] + public int TimeoutMinutes { get; set; } + } + + /// + /// Represents a EssentialsRoomTechConfig + /// + public class EssentialsRoomTechConfig + { + /// + /// Gets or sets the Password + /// + [JsonProperty("password")] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomEmergencyConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomEmergencyConfig.cs index f3440043..349f44b8 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomEmergencyConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomEmergencyConfig.cs @@ -1,29 +1,40 @@ -namespace PepperDash.Essentials.Room.Config; - -/// -/// -/// -public class EssentialsRoomEmergencyConfig -{ - public EssentialsRoomEmergencyTriggerConfig Trigger { get; set; } - - public string Behavior { get; set; } -} - -/// -/// -/// -public class EssentialsRoomEmergencyTriggerConfig +namespace PepperDash.Essentials.Room.Config { /// - /// contact,versiport + /// Represents a EssentialsRoomEmergencyConfig /// - public string Type { get; set; } + public class EssentialsRoomEmergencyConfig + { + /// + /// Gets or sets the Trigger + /// + public EssentialsRoomEmergencyTriggerConfig Trigger { get; set; } + + /// + /// Gets or sets the Behavior + /// + public string Behavior { get; set; } + } + /// - /// Input number if contact + /// Represents a EssentialsRoomEmergencyTriggerConfig /// - public int Number { get; set; } + public class EssentialsRoomEmergencyTriggerConfig + { + /// + /// contact,versiport + /// + public string Type { get; set; } - public bool TriggerOnClose { get; set; } + /// + /// Input number if contact + /// + public int Number { get; set; } + /// + /// TriggerOnClose indicates if the trigger is on close + /// + public bool TriggerOnClose { get; set; } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomScheduledEventsConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomScheduledEventsConfig.cs index b15bb9f8..d7cb0514 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomScheduledEventsConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsRoomScheduledEventsConfig.cs @@ -6,71 +6,71 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Room.Config; - - -/// -/// Represents a EssentialsRoomScheduledEventsConfig -/// -public class EssentialsRoomScheduledEventsConfig +namespace PepperDash.Essentials.Room.Config { /// - /// Gets or sets the ScheduledEvents + /// Represents a EssentialsRoomScheduledEventsConfig /// - [JsonProperty("scheduledEvents")] - public List ScheduledEvents; -} - -/// -/// Represents a ScheduledEventConfig -/// -public class ScheduledEventConfig -{ - /// - /// Gets or sets the Key - /// - [JsonProperty("key")] - public string Key; + public class EssentialsRoomScheduledEventsConfig + { + /// + /// Gets or sets the ScheduledEvents + /// + [JsonProperty("scheduledEvents")] + public List ScheduledEvents; + } /// - /// Gets or sets the Name + /// Represents a ScheduledEventConfig /// - [JsonProperty("name")] - public string Name; + public class ScheduledEventConfig + { + /// + /// Gets or sets the Key + /// + [JsonProperty("key")] + public string Key; - /// - /// Gets or sets the Days - /// - [JsonProperty("days")] - public ScheduledEventCommon.eWeekDays Days; + /// + /// Gets or sets the Name + /// + [JsonProperty("name")] + public string Name; - /// - /// Gets or sets the Time - /// - [JsonProperty("time")] - public string Time; + /// + /// Gets or sets the Days + /// + [JsonProperty("days")] + public ScheduledEventCommon.eWeekDays Days; - /// - /// Gets or sets the Actions - /// - [JsonProperty("actions")] - public List Actions; + /// + /// Gets or sets the Time + /// + [JsonProperty("time")] + public string Time; - /// - /// Gets or sets the Persistent - /// - [JsonProperty("persistent")] - public bool Persistent; + /// + /// Gets or sets the Actions + /// + [JsonProperty("actions")] + public List Actions; - /// - /// Gets or sets the Acknowledgeable - /// - [JsonProperty("acknowledgeable")] - public bool Acknowledgeable; + /// + /// Gets or sets the Persistent + /// + [JsonProperty("persistent")] + public bool Persistent; - /// - /// Gets or sets the Enable - /// - [JsonProperty("enable")] - public bool Enable; -} + /// + /// Gets or sets the Acknowledgeable + /// + [JsonProperty("acknowledgeable")] + public bool Acknowledgeable; + + /// + /// Gets or sets the Enable + /// + [JsonProperty("enable")] + public bool Enable; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsTechRoomConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsTechRoomConfig.cs index 086bf54f..dfc3d7a6 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/EssentialsTechRoomConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsTechRoomConfig.cs @@ -1,87 +1,89 @@  using System.Collections.Generic; using Newtonsoft.Json; +using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Room.Config; - -/// -/// Configuration class for the EssentialsTechRoom. This is used to deserialize the room configuration from JSON and provide it to the room on initialization. -/// -public class EssentialsTechRoomConfig +namespace PepperDash.Essentials.Room.Config { /// - /// The key of the dummy device used to enable routing + /// Represents a EssentialsTechRoomConfig /// - [JsonProperty("dummySourceKey")] - public string DummySourceKey { get; set; } - - /// - /// The keys of the displays assigned to this room - /// - [JsonProperty("displays")] - public List Displays { get; set; } - - /// - /// The keys of the tuners assigned to this room - /// - [JsonProperty("tuners")] - public List Tuners { get; set; } - - /// - /// PIN to access the room as a normal user - /// - [JsonProperty("userPin")] - public string UserPin { get; set; } - - /// - /// PIN to access the room as a tech user - /// - [JsonProperty("techPin")] - public string TechPin { get; set; } - - /// - /// Name of the presets file. Path prefix is assumed to be /html/presets/lists/ - /// - [JsonProperty("presetsFileName")] - public string PresetsFileName { get; set; } - - /// - /// List of scheduled events for the room - /// - [JsonProperty("scheduledEvents")] - public List ScheduledEvents { get; set; } - - /// - /// Indicates that the room is the primary when true - /// - [JsonProperty("isPrimary")] - public bool IsPrimary { get; set; } - - /// - /// Indicates which tuners should mirror preset recall when two rooms are configured in a primary->secondary scenario - /// - [JsonProperty("mirroredTuners")] - public Dictionary MirroredTuners { get; set; } - - /// - /// Help message to show on the UI when the user clicks the help button. Can be used to provide room specific instructions for users. - /// - [JsonProperty("helpMessage")] - public string HelpMessage { get; set; } - - /// - /// Indicates the room - /// - [JsonProperty("isTvPresetsProvider")] - public bool IsTvPresetsProvider; - - /// - /// Constructor - /// - public EssentialsTechRoomConfig() + public class EssentialsTechRoomConfig { - Displays = new List(); - Tuners = new List(); - ScheduledEvents = new List(); + /// + /// The key of the dummy device used to enable routing + /// + [JsonProperty("dummySourceKey")] + public string DummySourceKey { get; set; } + + /// + /// The keys of the displays assigned to this room + /// + [JsonProperty("displays")] + public List Displays { get; set; } + + /// + /// The keys of the tuners assigned to this room + /// + [JsonProperty("tuners")] + public List Tuners { get; set; } + + /// + /// PIN to access the room as a normal user + /// + [JsonProperty("userPin")] + public string UserPin { get; set; } + + /// + /// PIN to access the room as a tech user + /// + [JsonProperty("techPin")] + public string TechPin { get; set; } + + /// + /// Name of the presets file. Path prefix is assumed to be /html/presets/lists/ + /// + [JsonProperty("presetsFileName")] + public string PresetsFileName { get; set; } + + /// + /// Gets or sets the ScheduledEvents + /// + [JsonProperty("scheduledEvents")] + public List ScheduledEvents { get; set; } + + /// + /// Indicates that the room is the primary when true + /// + [JsonProperty("isPrimary")] + public bool IsPrimary { get; set; } + + /// + /// Indicates which tuners should mirror preset recall when two rooms are configured in a primary->secondary scenario + /// + [JsonProperty("mirroredTuners")] + public Dictionary MirroredTuners { get; set; } + + /// + /// Gets or sets the HelpMessage + /// + [JsonProperty("helpMessage")] + public string HelpMessage { get; set; } + + /// + /// Gets or sets the IsTvPresetsProvider + /// + [JsonProperty("isTvPresetsProvider")] + public bool IsTvPresetsProvider; + + /// + /// Constructor + /// + public EssentialsTechRoomConfig() + { + Displays = new List(); + Tuners = new List(); + ScheduledEvents = new List(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/EssentialsVolumeLevelConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/EssentialsVolumeLevelConfig.cs new file mode 100644 index 00000000..51172130 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Room/Config/EssentialsVolumeLevelConfig.cs @@ -0,0 +1,115 @@ +using System; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.Room.Config +{ + /// + /// Represents a EssentialsRoomVolumesConfig + /// + public class EssentialsRoomVolumesConfig + { + /// + /// Gets or sets the Master + /// + public EssentialsVolumeLevelConfig Master { get; set; } + + /// + /// Gets or sets the Program + /// + public EssentialsVolumeLevelConfig Program { get; set; } + + /// + /// Gets or sets the AudioCallRx + /// + public EssentialsVolumeLevelConfig AudioCallRx { get; set; } + + /// + /// Gets or sets the AudioCallTx + /// + public EssentialsVolumeLevelConfig AudioCallTx { get; set; } + } + + /// + /// Represents a EssentialsVolumeLevelConfig + /// + public class EssentialsVolumeLevelConfig + { + /// + /// Gets or sets the DeviceKey + /// + public string DeviceKey { get; set; } + /// + /// Gets or sets the Label + /// + public string Label { get; set; } + /// + /// Gets or sets the Level + /// + public int Level { get; set; } + + /// + /// Helper to get the device associated with key - one timer. + /// + public IBasicVolumeWithFeedback GetDevice() + { + throw new NotImplementedException("This method references DM CHASSIS Directly"); + /* + // DM output card format: deviceKey--output~number, dm8x8-1--output~4 + var match = Regex.Match(DeviceKey, @"([-_\w]+)--(\w+)~(\d+)"); + if (match.Success) + { + var devKey = match.Groups[1].Value; + var chassis = DeviceManager.GetDeviceForKey(devKey) as DmChassisController; + if (chassis != null) + { + var outputNum = Convert.ToUInt32(match.Groups[3].Value); + if (chassis.VolumeControls.ContainsKey(outputNum)) // should always... + return chassis.VolumeControls[outputNum]; + } + // No volume for some reason. We have failed as developers + return null; + } + + // DSP/DMPS format: deviceKey--levelName, biampTesira-1--master + match = Regex.Match(DeviceKey, @"([-_\w]+)--(.+)"); + if (match.Success) + { + var devKey = match.Groups[1].Value; + var dsp = DeviceManager.GetDeviceForKey(devKey) as BiampTesiraForteDsp; + if (dsp != null) + { + var levelTag = match.Groups[2].Value; + if (dsp.LevelControlPoints.ContainsKey(levelTag)) // should always... + return dsp.LevelControlPoints[levelTag]; + } + + var dmps = DeviceManager.GetDeviceForKey(devKey) as DmpsAudioOutputController; + if (dmps != null) + { + var levelTag = match.Groups[2].Value; + switch (levelTag) + { + case "master": + return dmps.MasterVolumeLevel; + case "source": + return dmps.SourceVolumeLevel; + case "micsmaster": + return dmps.MicsMasterVolumeLevel; + case "codec1": + return dmps.Codec1VolumeLevel; + case "codec2": + return dmps.Codec2VolumeLevel; + default: + return dmps.MasterVolumeLevel; + } + } + // No volume for some reason. We have failed as developers + return null; + } + + return null; + } + * */ + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Config/SimplRoomPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Room/Config/SimplRoomPropertiesConfig.cs index dad8b9f2..59628aed 100644 --- a/src/PepperDash.Essentials.Core/Room/Config/SimplRoomPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Room/Config/SimplRoomPropertiesConfig.cs @@ -1,52 +1,53 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace PepperDash.Essentials.Room.Config; - -/// -/// Represents a SimplRoomPropertiesConfig -/// -public class SimplRoomPropertiesConfig : EssentialsHuddleVtc1PropertiesConfig +namespace PepperDash.Essentials.Room.Config { - /// - /// Gets or sets the RoomPhoneNumber - /// - [JsonProperty("roomPhoneNumber")] - public string RoomPhoneNumber { get; set; } + /// + /// Represents a SimplRoomPropertiesConfig + /// + public class SimplRoomPropertiesConfig : EssentialsHuddleVtc1PropertiesConfig + { + /// + /// Gets or sets the RoomPhoneNumber + /// + [JsonProperty("roomPhoneNumber")] + public string RoomPhoneNumber { get; set; } + + /// + /// Gets or sets the RoomURI + /// + [JsonProperty("roomURI")] + public string RoomURI { get; set; } + + /// + /// Gets or sets the SpeedDials + /// + [JsonProperty("speedDials")] + public List SpeedDials { get; set; } + + /// + /// Gets or sets the VolumeSliderNames + /// + [JsonProperty("volumeSliderNames")] + public List VolumeSliderNames { get; set; } + } /// - /// Gets or sets the RoomURI + /// Represents a SimplSpeedDial /// - [JsonProperty("roomURI")] - public string RoomURI { get; set; } - - /// - /// Gets or sets the SpeedDials - /// - [JsonProperty("speedDials")] - public List SpeedDials { get; set; } - - /// - /// Gets or sets the VolumeSliderNames - /// - [JsonProperty("volumeSliderNames")] - public List VolumeSliderNames { get; set; } -} - -/// -/// Represents a SimplSpeedDial -/// -public class SimplSpeedDial -{ - /// - /// Gets or sets the Name - /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// Gets or sets the Number - /// - [JsonProperty("number")] - public string Number { get; set; } + public class SimplSpeedDial + { + /// + /// Gets or sets the Name + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets the Number + /// + [JsonProperty("number")] + public string Number { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/EsentialsRoomEmergencyContactClosure.cs b/src/PepperDash.Essentials.Core/Room/EsentialsRoomEmergencyContactClosure.cs index ee81c4d0..6582bac3 100644 --- a/src/PepperDash.Essentials.Core/Room/EsentialsRoomEmergencyContactClosure.cs +++ b/src/PepperDash.Essentials.Core/Room/EsentialsRoomEmergencyContactClosure.cs @@ -2,94 +2,116 @@ using Crestron.SimplSharpPro; using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Core; - -public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase, IEssentialsRoomEmergency +namespace PepperDash.Essentials.Core { - public event EventHandler EmergencyStateChange; - - IEssentialsRoom Room; - string Behavior; - bool TriggerOnClose; - - public bool InEmergency { get; private set; } - - public EssentialsRoomEmergencyContactClosure(string key, EssentialsRoomEmergencyConfig config, IEssentialsRoom room) : - base(key) + /// + /// Represents a EssentialsRoomEmergencyContactClosure + /// + public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase, IEssentialsRoomEmergency { - Room = room; - var cs = Global.ControlSystem; + /// + /// Event fired when emergency state changes + /// + public event EventHandler EmergencyStateChange; - if (config.Trigger.Type.Equals("contact", StringComparison.OrdinalIgnoreCase)) + IEssentialsRoom Room; + string Behavior; + bool TriggerOnClose; + + /// + /// Gets or sets the InEmergency + /// + public bool InEmergency { get; private set; } + + /// + /// Constructor for EssentialsRoomEmergencyContactClosure + /// + /// device key + /// emergency device config + /// the room associated with this emergency contact closure + public EssentialsRoomEmergencyContactClosure(string key, EssentialsRoomEmergencyConfig config, IEssentialsRoom room) : + base(key) { - var portNum = (uint)config.Trigger.Number; - if (portNum <= cs.NumberOfDigitalInputPorts) + Room = room; + var cs = Global.ControlSystem; + + if (config.Trigger.Type.Equals("contact", StringComparison.OrdinalIgnoreCase)) { - cs.DigitalInputPorts[portNum].Register(); - cs.DigitalInputPorts[portNum].StateChange += EsentialsRoomEmergencyContactClosure_StateChange; + var portNum = (uint)config.Trigger.Number; + if (portNum <= cs.NumberOfDigitalInputPorts) + { + cs.DigitalInputPorts[portNum].Register(); + cs.DigitalInputPorts[portNum].StateChange += EsentialsRoomEmergencyContactClosure_StateChange; + } + } + else if (config.Trigger.Type.Equals("versiport", StringComparison.OrdinalIgnoreCase)) + { + var portNum = (uint)config.Trigger.Number; + if (portNum <= cs.NumberOfVersiPorts) + { + cs.VersiPorts[portNum].Register(); + cs.VersiPorts[portNum].SetVersiportConfiguration(eVersiportConfiguration.DigitalInput); + cs.VersiPorts[portNum].DisablePullUpResistor = true; + cs.VersiPorts[portNum].VersiportChange += EssentialsRoomEmergencyContactClosure_VersiportChange; + } + } + Behavior = config.Behavior; + TriggerOnClose = config.Trigger.TriggerOnClose; + } + + private void EssentialsRoomEmergencyContactClosure_VersiportChange(Versiport port, VersiportEventArgs args) + { + if (args.Event == eVersiportEvent.DigitalInChange) + { + ContactClosure_StateChange(port.DigitalIn); } } - else if (config.Trigger.Type.Equals("versiport", StringComparison.OrdinalIgnoreCase)) + + void EsentialsRoomEmergencyContactClosure_StateChange(DigitalInput digitalInput, DigitalInputEventArgs args) { - var portNum = (uint)config.Trigger.Number; - if (portNum <= cs.NumberOfVersiPorts) + ContactClosure_StateChange(args.State); + } + + void ContactClosure_StateChange(bool portState) + { + if (portState && TriggerOnClose || !portState && !TriggerOnClose) { - cs.VersiPorts[portNum].Register(); - cs.VersiPorts[portNum].SetVersiportConfiguration(eVersiportConfiguration.DigitalInput); - cs.VersiPorts[portNum].DisablePullUpResistor = true; - cs.VersiPorts[portNum].VersiportChange += EssentialsRoomEmergencyContactClosure_VersiportChange; + InEmergency = true; + if (EmergencyStateChange != null) + EmergencyStateChange(this, new EventArgs()); + RunEmergencyBehavior(); + } + else + { + InEmergency = false; + if (EmergencyStateChange != null) + EmergencyStateChange(this, new EventArgs()); } } - Behavior = config.Behavior; - TriggerOnClose = config.Trigger.TriggerOnClose; - } - private void EssentialsRoomEmergencyContactClosure_VersiportChange(Versiport port, VersiportEventArgs args) - { - if (args.Event == eVersiportEvent.DigitalInChange) + /// + /// RunEmergencyBehavior method + /// + public void RunEmergencyBehavior() { - ContactClosure_StateChange(port.DigitalIn); - } - } - - void EsentialsRoomEmergencyContactClosure_StateChange(DigitalInput digitalInput, DigitalInputEventArgs args) - { - ContactClosure_StateChange(args.State); - } - - void ContactClosure_StateChange(bool portState) - { - if (portState && TriggerOnClose || !portState && !TriggerOnClose) - { - InEmergency = true; - if (EmergencyStateChange != null) - EmergencyStateChange(this, new EventArgs()); - RunEmergencyBehavior(); - } - else - { - InEmergency = false; - if (EmergencyStateChange != null) - EmergencyStateChange(this, new EventArgs()); + if (Behavior.Equals("shutdown")) + Room.Shutdown(); } } /// - /// + /// Defines the contract for IEssentialsRoomEmergency /// - public void RunEmergencyBehavior() + public interface IEssentialsRoomEmergency { - if (Behavior.Equals("shutdown")) - Room.Shutdown(); + /// + /// Event fired when emergency state changes + /// + event EventHandler EmergencyStateChange; + + /// + /// Gets or sets the InEmergency + /// + bool InEmergency { get; } } -} - -/// -/// Describes the functionality of a room emergency contact closure -/// -public interface IEssentialsRoomEmergency -{ - event EventHandler EmergencyStateChange; - - bool InEmergency { get; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs index 58e44bb4..13ba9d66 100644 --- a/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs +++ b/src/PepperDash.Essentials.Core/Room/EssentialsRoomBase.cs @@ -12,188 +12,259 @@ using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// -/// -public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom +namespace PepperDash.Essentials.Core { /// - /// + /// /// - public BoolFeedback OnFeedback { get; private set; } - - /// - /// Fires when the RoomOccupancy object is set - /// - public event EventHandler RoomOccupancyIsSet; - - public BoolFeedback IsWarmingUpFeedback { get; private set; } - public BoolFeedback IsCoolingDownFeedback { get; private set; } - - public IOccupancyStatusProvider RoomOccupancy { get; protected set; } - - public bool OccupancyStatusProviderIsRemote { get; private set; } - - public List EnvironmentalControlDevices { get; protected set; } - - public bool HasEnvironmentalControlDevices + public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom { - get + /// + /// + /// + public BoolFeedback OnFeedback { get; private set; } + + /// + /// Fires when the RoomOccupancy object is set + /// + public event EventHandler RoomOccupancyIsSet; + + /// + /// Gets or sets the IsWarmingUpFeedback + /// + public BoolFeedback IsWarmingUpFeedback { get; private set; } + + /// + /// Gets or sets the IsCoolingDownFeedback + /// + public BoolFeedback IsCoolingDownFeedback { get; private set; } + + /// + /// Gets or sets the RoomOccupancy + /// + public IOccupancyStatusProvider RoomOccupancy { get; protected set; } + + /// + /// Gets or sets the OccupancyStatusProviderIsRemote + /// + public bool OccupancyStatusProviderIsRemote { get; private set; } + + /// + /// Gets or sets the EnvironmentalControlDevices + /// + public List EnvironmentalControlDevices { get; protected set; } + + /// + /// Indicates if the room has any environmental control devices + /// + public bool HasEnvironmentalControlDevices { - return EnvironmentalControlDevices != null && EnvironmentalControlDevices.Count > 0; + get + { + return EnvironmentalControlDevices != null && EnvironmentalControlDevices.Count > 0; + } } - } - protected abstract Func IsWarmingFeedbackFunc { get; } - protected abstract Func IsCoolingFeedbackFunc { get; } + /// + /// Gets or sets the IsWarmingFeedbackFunc + /// + protected abstract Func IsWarmingFeedbackFunc { get; } - /// - /// Indicates if this room is Mobile Control Enabled - /// - public bool IsMobileControlEnabled { get; private set; } + /// + /// Gets or sets the IsCoolingFeedbackFunc + /// + protected abstract Func IsCoolingFeedbackFunc { get; } - /// - /// The bridge for this room if Mobile Control is enabled - /// - public IMobileControlRoomMessenger MobileControlRoomBridge { get; private set; } + /// + /// Gets or sets the IsMobileControlEnabled + /// + public bool IsMobileControlEnabled { get; private set; } - protected const string _defaultListKey = "default"; + /// + /// Gets or sets the MobileControlRoomBridge + /// + public IMobileControlRoomMessenger MobileControlRoomBridge { get; private set; } - /// - /// The config name of the source list - /// + /// + /// The config name of the default source list + /// + protected const string _defaultListKey = "default"; + + /// + /// The config name of the source list + /// /// private string _sourceListKey; - public string SourceListKey { + + /// + /// Gets or sets the SourceListKey + /// + public string SourceListKey { get { - if(string.IsNullOrEmpty(_sourceListKey)) - { - return _defaultListKey; - } - else - { - return _sourceListKey; - } + if(string.IsNullOrEmpty(_sourceListKey)) + { + return _defaultListKey; + } + else + { + return _sourceListKey; + } } protected set { - if (value != _sourceListKey) - { - _sourceListKey = value; - } + if (value != _sourceListKey) + { + _sourceListKey = value; + } } } - private string _destinationListKey; - public string DestinationListKey - { - get + private string _destinationListKey; + + /// + /// Gets or sets the DestinationListKey + /// + public string DestinationListKey { - if (string.IsNullOrEmpty(_destinationListKey)) + get { - return _defaultListKey; + if (string.IsNullOrEmpty(_destinationListKey)) + { + return _defaultListKey; + } + else + { + return _destinationListKey; + } } - else + protected set { - return _destinationListKey; + if (value != _destinationListKey) + { + _destinationListKey = value; + } } } - protected set + + private string _audioControlPointListKey; + + /// + /// Gets or sets the AudioControlPointListKey + /// + public string AudioControlPointListKey { - if (value != _destinationListKey) + get { - _destinationListKey = value; + if (string.IsNullOrEmpty(_audioControlPointListKey)) + { + return _defaultListKey; + } + else + { + return _destinationListKey; + } + } + protected set + { + if (value != _audioControlPointListKey) + { + _audioControlPointListKey = value; + } } } - } - private string _audioControlPointListKey; - public string AudioControlPointListKey - { - get + private string _cameraListKey; + + /// + /// Gets or sets the CameraListKey + /// + public string CameraListKey { - if (string.IsNullOrEmpty(_audioControlPointListKey)) + get { - return _defaultListKey; + if (string.IsNullOrEmpty(_cameraListKey)) + { + return _defaultListKey; + } + else + { + return _cameraListKey; + } } - else + protected set { - return _destinationListKey; + if (value != _cameraListKey) + { + _cameraListKey = value; + } } } - protected set - { - if (value != _audioControlPointListKey) - { - _audioControlPointListKey = value; - } - } - } - private string _cameraListKey; - public string CameraListKey - { - get - { - if (string.IsNullOrEmpty(_cameraListKey)) - { - return _defaultListKey; - } - else - { - return _cameraListKey; - } - } - protected set - { - if (value != _cameraListKey) - { - _cameraListKey = value; - } - } - } + /// + /// Gets or sets the ShutdownPromptTimer + /// + public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } - /// - /// Timer used for informing the UIs of a shutdown - /// - public SecondsCountdownTimer ShutdownPromptTimer { get; private set; } + /// + /// Gets or sets the ShutdownPromptSeconds + /// + public int ShutdownPromptSeconds { get; set; } - /// - /// - /// - public int ShutdownPromptSeconds { get; set; } - public int ShutdownVacancySeconds { get; set; } - public eShutdownType ShutdownType { get; private set; } + /// + /// Gets or sets the ShutdownVacancySeconds + /// + public int ShutdownVacancySeconds { get; set; } - public EssentialsRoomEmergencyBase Emergency { get; set; } + /// + /// Gets or sets the ShutdownType + /// + public eShutdownType ShutdownType { get; private set; } - public Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; set; } + /// + /// Gets or sets the Emergency + /// + public EssentialsRoomEmergencyBase Emergency { get; set; } - public string LogoUrlLightBkgnd { get; set; } + /// + /// Gets or sets the MicrophonePrivacy + /// + public Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; set; } - public string LogoUrlDarkBkgnd { get; set; } + /// + /// Gets or sets the LogoUrlLightBkgnd + /// + public string LogoUrlLightBkgnd { get; set; } - protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; } + /// + /// Gets or sets the LogoUrlDarkBkgnd + /// + public string LogoUrlDarkBkgnd { get; set; } - public eVacancyMode VacancyMode { get; private set; } + /// + /// Gets or sets the RoomVacancyShutdownTimer + /// + protected SecondsCountdownTimer RoomVacancyShutdownTimer { get; private set; } - /// - /// Seconds after vacancy prompt is displayed until shutdown - /// - protected int RoomVacancyShutdownSeconds; + /// + /// Gets or sets the VacancyMode + /// + public eVacancyMode VacancyMode { get; private set; } - /// - /// Seconds after vacancy detected until prompt is displayed - /// - protected int RoomVacancyShutdownPromptSeconds; + /// + /// Seconds after vacancy prompt is displayed until shutdown + /// + protected int RoomVacancyShutdownSeconds; - /// - /// - /// - protected abstract Func OnFeedbackFunc { get; } + /// + /// Seconds after vacancy detected until prompt is displayed + /// + protected int RoomVacancyShutdownPromptSeconds; + + /// + /// + /// + protected abstract Func OnFeedbackFunc { get; } /// /// Gets or sets the SavedVolumeLevels @@ -205,293 +276,362 @@ public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom /// public bool ZeroVolumeWhenSwtichingVolumeDevices { get; private set; } - - public EssentialsRoomBase(DeviceConfig config) - : base(config) - { - EnvironmentalControlDevices = new List(); - - // Setup the ShutdownPromptTimer - ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); - ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => + /// + /// Constructor for EssentialsRoomBase + /// + /// config of the device + public EssentialsRoomBase(DeviceConfig config) + : base(config) { - if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) - ShutdownType = eShutdownType.None; - }; + EnvironmentalControlDevices = new List(); - ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered + // Setup the ShutdownPromptTimer + ShutdownPromptTimer = new SecondsCountdownTimer(Key + "-offTimer"); + ShutdownPromptTimer.IsRunningFeedback.OutputChange += (o, a) => + { + if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue) + ShutdownType = eShutdownType.None; + }; - ShutdownPromptSeconds = 60; - ShutdownVacancySeconds = 120; - - ShutdownType = eShutdownType.None; + ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered - RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); - //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => - //{ - // if (!RoomVacancyShutdownTimer.IsRunningFeedback.BoolValue) - // ShutdownType = ShutdownType.Vacancy; - //}; - RoomVacancyShutdownTimer.HasFinished += new EventHandler(RoomVacancyShutdownPromptTimer_HasFinished); // Shutdown is triggered + ShutdownPromptSeconds = 60; + ShutdownVacancySeconds = 120; + + ShutdownType = eShutdownType.None; - RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning - RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt - VacancyMode = eVacancyMode.None; + RoomVacancyShutdownTimer = new SecondsCountdownTimer(Key + "-vacancyOffTimer"); + //RoomVacancyShutdownTimer.IsRunningFeedback.OutputChange += (o, a) => + //{ + // if (!RoomVacancyShutdownTimer.IsRunningFeedback.BoolValue) + // ShutdownType = ShutdownType.Vacancy; + //}; + RoomVacancyShutdownTimer.HasFinished += new EventHandler(RoomVacancyShutdownPromptTimer_HasFinished); // Shutdown is triggered - OnFeedback = new BoolFeedback(OnFeedbackFunc); + RoomVacancyShutdownPromptSeconds = 1500; // 25 min to prompt warning + RoomVacancyShutdownSeconds = 240; // 4 min after prompt will trigger shutdown prompt + VacancyMode = eVacancyMode.None; - IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); - IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); + OnFeedback = new BoolFeedback(OnFeedbackFunc); - AddPostActivationAction(() => - { - if (RoomOccupancy != null) - OnRoomOccupancyIsSet(); - }); - } + IsWarmingUpFeedback = new BoolFeedback(IsWarmingFeedbackFunc); + IsCoolingDownFeedback = new BoolFeedback(IsCoolingFeedbackFunc); - protected override bool CustomActivate() - { - SetUpMobileControl(); - - return base.CustomActivate(); - } - - /// - /// Sets the SourceListKey property to the passed in value or the default if no value passed in - /// - /// - protected void SetSourceListKey(string sourceListKey) - { - if (!string.IsNullOrEmpty(sourceListKey)) - { - SourceListKey = sourceListKey; + AddPostActivationAction(() => + { + if (RoomOccupancy != null) + OnRoomOccupancyIsSet(); + }); } - else - { - sourceListKey = _defaultListKey; - } - } - protected void SetDestinationListKey(string destinationListKey) - { - if (!string.IsNullOrEmpty(destinationListKey)) + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() { - DestinationListKey = destinationListKey; - } - } + SetUpMobileControl(); - /// - /// If mobile control is enabled, sets the appropriate properties - /// - void SetUpMobileControl() - { - var mcBridgeKey = string.Format("mobileControlBridge-{0}", Key); - var mcBridge = DeviceManager.GetDeviceForKey(mcBridgeKey); - if (mcBridge == null) - { - Debug.LogMessage(LogEventLevel.Debug, this, "*********************Mobile Control Bridge Not found for this room."); - IsMobileControlEnabled = false; - return; + return base.CustomActivate(); } - else - { - MobileControlRoomBridge = mcBridge as IMobileControlRoomMessenger; - Debug.LogMessage(LogEventLevel.Debug, this, "*********************Mobile Control Bridge found and enabled for this room"); - IsMobileControlEnabled = true; - } - } - void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e) - { - switch (VacancyMode) + /// + /// Sets the SourceListKey property to the passed in value or the default if no value passed in + /// + /// + protected void SetSourceListKey(string sourceListKey) { - case eVacancyMode.None: - StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); - break; - case eVacancyMode.InInitialVacancy: - StartRoomVacancyTimer(eVacancyMode.InShutdownWarning); - break; - case eVacancyMode.InShutdownWarning: - { - StartShutdown(eShutdownType.Vacancy); - Debug.LogMessage(LogEventLevel.Information, this, "Shutting Down due to vacancy."); + if (!string.IsNullOrEmpty(sourceListKey)) + { + SourceListKey = sourceListKey; + } + else + { + sourceListKey = _defaultListKey; + } + } + + /// + /// Sets the DestinationListKey property to the passed in value or the default if no value passed in + /// + /// key of the destination list object + protected void SetDestinationListKey(string destinationListKey) + { + if (!string.IsNullOrEmpty(destinationListKey)) + { + DestinationListKey = destinationListKey; + } + } + + /// + /// If mobile control is enabled, sets the appropriate properties + /// + void SetUpMobileControl() + { + var mcBridgeKey = string.Format("mobileControlBridge-{0}", Key); + var mcBridge = DeviceManager.GetDeviceForKey(mcBridgeKey); + if (mcBridge == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "*********************Mobile Control Bridge Not found for this room."); + IsMobileControlEnabled = false; + return; + } + else + { + MobileControlRoomBridge = mcBridge as IMobileControlRoomMessenger; + Debug.LogMessage(LogEventLevel.Debug, this, "*********************Mobile Control Bridge found and enabled for this room"); + IsMobileControlEnabled = true; + } + } + + void RoomVacancyShutdownPromptTimer_HasFinished(object sender, EventArgs e) + { + switch (VacancyMode) + { + case eVacancyMode.None: + StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); break; - } - default: - break; + case eVacancyMode.InInitialVacancy: + StartRoomVacancyTimer(eVacancyMode.InShutdownWarning); + break; + case eVacancyMode.InShutdownWarning: + { + StartShutdown(eShutdownType.Vacancy); + Debug.LogMessage(LogEventLevel.Information, this, "Shutting Down due to vacancy."); + break; + } + default: + break; + } } - } - /// - /// - /// - /// - public void StartShutdown(eShutdownType type) - { - // Check for shutdowns running. Manual should override other shutdowns + /// + /// + /// + /// + /// + /// StartShutdown method + /// + public void StartShutdown(eShutdownType type) + { + // Check for shutdowns running. Manual should override other shutdowns - if (type == eShutdownType.Manual) - ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; - else if (type == eShutdownType.Vacancy) - ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; - ShutdownType = type; - ShutdownPromptTimer.Start(); + if (type == eShutdownType.Manual) + ShutdownPromptTimer.SecondsToCount = ShutdownPromptSeconds; + else if (type == eShutdownType.Vacancy) + ShutdownPromptTimer.SecondsToCount = ShutdownVacancySeconds; + ShutdownType = type; + ShutdownPromptTimer.Start(); - Debug.LogMessage(LogEventLevel.Information, this, "ShutdownPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount); - } + Debug.LogMessage(LogEventLevel.Information, this, "ShutdownPromptTimer Started. Type: {0}. Seconds: {1}", ShutdownType, ShutdownPromptTimer.SecondsToCount); + } - public void StartRoomVacancyTimer(eVacancyMode mode) - { - if (mode == eVacancyMode.None) - RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownPromptSeconds; - else if (mode == eVacancyMode.InInitialVacancy) - RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds; - else if (mode == eVacancyMode.InShutdownWarning) - RoomVacancyShutdownTimer.SecondsToCount = 60; - VacancyMode = mode; - RoomVacancyShutdownTimer.Start(); + /// + /// StartRoomVacancyTimer method + /// + public void StartRoomVacancyTimer(eVacancyMode mode) + { + if (mode == eVacancyMode.None) + RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownPromptSeconds; + else if (mode == eVacancyMode.InInitialVacancy) + RoomVacancyShutdownTimer.SecondsToCount = RoomVacancyShutdownSeconds; + else if (mode == eVacancyMode.InShutdownWarning) + RoomVacancyShutdownTimer.SecondsToCount = 60; + VacancyMode = mode; + RoomVacancyShutdownTimer.Start(); - Debug.LogMessage(LogEventLevel.Information, this, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount); - } + Debug.LogMessage(LogEventLevel.Information, this, "Vacancy Timer Started. Mode: {0}. Seconds: {1}", VacancyMode, RoomVacancyShutdownTimer.SecondsToCount); + } - /// - /// Resets the vacancy mode and shutsdwon the room - /// - public void Shutdown() - { - VacancyMode = eVacancyMode.None; - EndShutdown(); - } + /// + /// Shutdown method + /// + public void Shutdown() + { + VacancyMode = eVacancyMode.None; + EndShutdown(); + } - /// - /// This method is for the derived class to define it's specific shutdown - /// requirements but should not be called directly. It is called by Shutdown() - /// - protected abstract void EndShutdown(); + /// + /// This method is for the derived class to define it's specific shutdown + /// requirements but should not be called directly. It is called by Shutdown() + /// + protected abstract void EndShutdown(); - /// - /// Override this to implement a default volume level(s) method - /// - public abstract void SetDefaultLevels(); + /// + /// Override this to implement a default volume level(s) method + /// + public abstract void SetDefaultLevels(); - /// - /// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device - /// - /// - public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes) - { + /// + /// Sets the object to be used as the IOccupancyStatusProvider for the room. Can be an Occupancy Aggregator or a specific device + /// + /// + /// + public void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes) + { if (statusProvider == null) { Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Occupancy sensor device is null"); return; } - Debug.LogMessage(LogEventLevel.Information, this, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key); - Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes); + Debug.LogMessage(LogEventLevel.Information, this, "Room Occupancy set to device: '{0}'", (statusProvider as Device).Key); + Debug.LogMessage(LogEventLevel.Information, this, "Timeout Minutes from Config is: {0}", timeoutMinutes); - // If status provider is fusion, set flag to remote - if (statusProvider is Core.Fusion.IEssentialsRoomFusionController) - OccupancyStatusProviderIsRemote = true; + // If status provider is fusion, set flag to remote + if (statusProvider is Core.Fusion.IEssentialsRoomFusionController) + OccupancyStatusProviderIsRemote = true; - if(timeoutMinutes > 0) - RoomVacancyShutdownSeconds = timeoutMinutes * 60; + if(timeoutMinutes > 0) + RoomVacancyShutdownSeconds = timeoutMinutes * 60; - Debug.LogMessage(LogEventLevel.Information, this, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds); + Debug.LogMessage(LogEventLevel.Information, this, "RoomVacancyShutdownSeconds set to {0}", RoomVacancyShutdownSeconds); - RoomOccupancy = statusProvider; + RoomOccupancy = statusProvider; - RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; - RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; + RoomOccupancy.RoomIsOccupiedFeedback.OutputChange -= RoomIsOccupiedFeedback_OutputChange; + RoomOccupancy.RoomIsOccupiedFeedback.OutputChange += RoomIsOccupiedFeedback_OutputChange; - OnRoomOccupancyIsSet(); - } - - void OnRoomOccupancyIsSet() - { - var handler = RoomOccupancyIsSet; - if (handler != null) - handler(this, new EventArgs()); - } - - /// - /// To allow base class to power room on to last source - /// - public abstract void PowerOnToDefaultOrLastSource(); - - /// - /// To allow base class to power room on to default source - /// - /// - public abstract bool RunDefaultPresentRoute(); - - void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) - { - if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false && AllowVacancyTimerToStart()) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Notice: Vacancy Detected"); - // Trigger the timer when the room is vacant - StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); + OnRoomOccupancyIsSet(); } - else + + void OnRoomOccupancyIsSet() { - Debug.LogMessage(LogEventLevel.Debug, this, "Notice: Occupancy Detected"); - // Reset the timer when the room is occupied - RoomVacancyShutdownTimer.Cancel(); + var handler = RoomOccupancyIsSet; + if (handler != null) + handler(this, new EventArgs()); + } + + /// + /// To allow base class to power room on to last source + /// + public abstract void PowerOnToDefaultOrLastSource(); + + /// + /// To allow base class to power room on to default source + /// + /// + public abstract bool RunDefaultPresentRoute(); + + void RoomIsOccupiedFeedback_OutputChange(object sender, EventArgs e) + { + if (RoomOccupancy.RoomIsOccupiedFeedback.BoolValue == false && AllowVacancyTimerToStart()) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Notice: Vacancy Detected"); + // Trigger the timer when the room is vacant + StartRoomVacancyTimer(eVacancyMode.InInitialVacancy); + } + else + { + Debug.LogMessage(LogEventLevel.Debug, this, "Notice: Occupancy Detected"); + // Reset the timer when the room is occupied + RoomVacancyShutdownTimer.Cancel(); + } + } + + /// + /// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed + /// + /// + public abstract void RoomVacatedForTimeoutPeriod(object o); + + /// + /// Allow the vacancy event from an occupancy sensor to turn the room off. + /// + /// If the timer should be allowed. Defaults to true + protected virtual bool AllowVacancyTimerToStart() + { + return true; } } - + /// - /// Executes when RoomVacancyShutdownTimer expires. Used to trigger specific room actions as needed. Must nullify the timer object when executed + /// To describe the various ways a room may be shutting down /// - /// - public abstract void RoomVacatedForTimeoutPeriod(object o); - - /// - /// Allow the vacancy event from an occupancy sensor to turn the room off. - /// - /// If the timer should be allowed. Defaults to true - protected virtual bool AllowVacancyTimerToStart() + public enum eShutdownType { - return true; + /// + /// No shutdown in progress + /// + None = 0, + + /// + /// Manual shutdown initiated + /// + External, + + /// + /// Vacancy based shutdown + /// + Manual, + + /// + /// Shutdown due to room vacancy + /// + Vacancy } -} - -/// -/// To describe the various ways a room may be shutting down -/// -public enum eShutdownType -{ - None = 0, - External, - Manual, - Vacancy -} -public enum eVacancyMode -{ - None = 0, - InInitialVacancy, - InShutdownWarning -} - -/// -/// -/// -public enum eWarmingCoolingMode -{ - None, - Warming, - Cooling -} - -public abstract class EssentialsRoomEmergencyBase : IKeyed -{ - public string Key { get; private set; } - - public EssentialsRoomEmergencyBase(string key) + /// + /// Enumeration of eVacancyMode values + /// + public enum eVacancyMode { - Key = key; + /// + /// No vacancy detected + /// + None = 0, + + /// + /// InInitialVacancy - countdown to warning + /// + InInitialVacancy, + + /// + /// InShutdownWarning - countdown to shutdown + /// + InShutdownWarning + } + + /// + /// Enumeration of eWarmingCoolingMode values + /// + public enum eWarmingCoolingMode + { + /// + /// None + /// + None, + + /// + /// Warming + /// + Warming, + + /// + /// Cooling + /// + Cooling + } + + /// + /// Base class for room emergency implementations + /// + public abstract class EssentialsRoomEmergencyBase : IKeyed + { + /// + /// Key of the room + /// + public string Key { get; private set; } + + /// + /// Constructor for EssentialsRoomEmergencyBase + /// + /// key of the room + public EssentialsRoomEmergencyBase(string key) + { + Key = key; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs b/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs index 681b6675..3ebe28c4 100644 --- a/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs +++ b/src/PepperDash.Essentials.Core/Room/IEssentialsRoom.cs @@ -10,101 +10,103 @@ using PepperDash.Essentials.Core.Devices; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Describes the basic functionality of an EssentialsRoom -/// -public interface IEssentialsRoom : IKeyName, IReconfigurableDevice, IRunDefaultPresentRoute, IEnvironmentalControls +namespace PepperDash.Essentials.Core { /// - /// Gets the PowerFeedback + /// Describes the basic functionality of an EssentialsRoom /// - BoolFeedback OnFeedback { get; } + public interface IEssentialsRoom : IKeyName, IReconfigurableDevice, IRunDefaultPresentRoute, IEnvironmentalControls + { + /// + /// Gets the PowerFeedback + /// + BoolFeedback OnFeedback { get; } - /// - /// Gets the IsOccupiedFeedback - /// - BoolFeedback IsWarmingUpFeedback { get; } + /// + /// Gets the IsOccupiedFeedback + /// + BoolFeedback IsWarmingUpFeedback { get; } - /// - /// Gets the IsCoolingDownFeedback - /// - BoolFeedback IsCoolingDownFeedback { get; } + /// + /// Gets the IsCoolingDownFeedback + /// + BoolFeedback IsCoolingDownFeedback { get; } - /// - /// Gets a value indicating whether mobile control is enabled for this room - /// - bool IsMobileControlEnabled { get; } + /// + /// Gets a value indicating whether mobile control is enabled for this room + /// + bool IsMobileControlEnabled { get; } - /// - /// Gets the MobileControlRoomBridge - /// - IMobileControlRoomMessenger MobileControlRoomBridge { get; } + /// + /// Gets the MobileControlRoomBridge + /// + IMobileControlRoomMessenger MobileControlRoomBridge { get; } - /// - /// Gets the SourceListKey - /// - string SourceListKey { get; } + /// + /// Gets the SourceListKey + /// + string SourceListKey { get; } - /// - /// Gets the DestinationListKey - /// - string DestinationListKey { get; } + /// + /// Gets the DestinationListKey + /// + string DestinationListKey { get; } - /// - /// Gets the AudioControlPointListKey - /// - string AudioControlPointListKey { get; } + /// + /// Gets the AudioControlPointListKey + /// + string AudioControlPointListKey { get; } - /// - /// Gets the CameraListKey - /// - string CameraListKey { get; } + /// + /// Gets the CameraListKey + /// + string CameraListKey { get; } - /// - /// Gets the ShutdownPromptTimer - /// - SecondsCountdownTimer ShutdownPromptTimer { get; } + /// + /// Gets the ShutdownPromptTimer + /// + SecondsCountdownTimer ShutdownPromptTimer { get; } - /// - /// Gets the ShutdownVacancyTimer - /// - int ShutdownPromptSeconds { get; } + /// + /// Gets the ShutdownVacancyTimer + /// + int ShutdownPromptSeconds { get; } - /// - /// Gets the ShutdownVacancySeconds - /// - int ShutdownVacancySeconds { get; } + /// + /// Gets the ShutdownVacancySeconds + /// + int ShutdownVacancySeconds { get; } - /// - /// Gets the ShutdownType - /// - eShutdownType ShutdownType { get; } + /// + /// Gets the ShutdownType + /// + eShutdownType ShutdownType { get; } - /// - /// Gets the LogoUrlLightBkgnd - /// - string LogoUrlLightBkgnd { get; } + /// + /// Gets the LogoUrlLightBkgnd + /// + string LogoUrlLightBkgnd { get; } - /// - /// Gets the LogoUrlDarkBkgnd - /// - string LogoUrlDarkBkgnd { get; } + /// + /// Gets the LogoUrlDarkBkgnd + /// + string LogoUrlDarkBkgnd { get; } - /// - /// Starts the shutdown process - /// - /// type of shutdown event - void StartShutdown(eShutdownType type); + /// + /// Starts the shutdown process + /// + /// type of shutdown event + void StartShutdown(eShutdownType type); - /// - /// Shuts down the room - /// - void Shutdown(); + /// + /// Shuts down the room + /// + void Shutdown(); - /// - /// Powers on the room to either the default source or the last source used - /// - void PowerOnToDefaultOrLastSource(); -} + /// + /// Powers on the room to either the default source or the last source used + /// + void PowerOnToDefaultOrLastSource(); + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/IRoomEventSchedule.cs b/src/PepperDash.Essentials.Core/Room/IRoomEventSchedule.cs index 8f841def..a42ea505 100644 --- a/src/PepperDash.Essentials.Core/Room/IRoomEventSchedule.cs +++ b/src/PepperDash.Essentials.Core/Room/IRoomEventSchedule.cs @@ -2,40 +2,39 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IRoomEventSchedule -/// -public interface IRoomEventSchedule +namespace PepperDash.Essentials.Core { /// - /// Adds or updates a scheduled event + /// Defines the contract for IRoomEventSchedule /// - /// - void AddOrUpdateScheduledEvent(ScheduledEventConfig eventConfig); + public interface IRoomEventSchedule + { + /// + /// Adds or updates a scheduled event + /// + /// + void AddOrUpdateScheduledEvent(ScheduledEventConfig eventConfig); + + /// + /// Removes a scheduled event by its key + /// + /// + List GetScheduledEvents(); + + /// + /// Removes a scheduled event by its key + /// + event EventHandler ScheduledEventsChanged; + } /// - /// Removes a scheduled event by its key + /// Represents a ScheduledEventEventArgs /// - /// - List GetScheduledEvents(); - - /// - /// Removes a scheduled event by its key - /// - event EventHandler ScheduledEventsChanged; + public class ScheduledEventEventArgs : EventArgs + { + /// + /// Gets or sets the ScheduledEvents + /// + public List ScheduledEvents; + } } - -/// -/// Represents a ScheduledEventEventArgs -/// -public class ScheduledEventEventArgs : EventArgs -{ - /// - /// Gets or sets the ScheduledEvents - /// - public List ScheduledEvents; -} - diff --git a/src/PepperDash.Essentials.Core/Room/Interfaces.cs b/src/PepperDash.Essentials.Core/Room/Interfaces.cs index 499e66a4..0d2f0850 100644 --- a/src/PepperDash.Essentials.Core/Room/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Room/Interfaces.cs @@ -4,296 +4,307 @@ using System.Collections.Generic; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// For rooms with in call feedback -/// -public interface IHasInCallFeedback +namespace PepperDash.Essentials.Core { /// - /// Feedback indicating whether the room is currently in a call + /// For rooms with in call feedback /// - BoolFeedback InCallFeedback { get; } -} - -/// -/// For rooms with a single display -/// -public interface IHasDefaultDisplay -{ - /// - /// The default display for the room, used for presentation routing and other default routes - /// - IRoutingSinkWithFeedback DefaultDisplay { get; } -} - -/// -/// For rooms with routing -/// -public interface IRunRouteAction -{ - /// - /// Runs a route action for a given route and source list key. - /// The source list key is used to determine the source for the route from the source list in the room's routing controller. - /// This allows for dynamic routing based on what source is selected in the UI for a given route. - /// - /// - /// - void RunRouteAction(string routeKey, string sourceListKey); - - /// - /// Runs a route action for a given route and source list key with a callback for success. - /// The source list key is used to determine the source for the route from the source list in the room's routing controller. - /// This allows for dynamic routing based on what source is selected in the UI for a given route. - /// - /// - /// - /// - void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); -} - -/// -/// Simplified routing direct from source to destination -/// -public interface IRunDirectRouteAction -{ - /// - /// Runs a direct route from a source to a destination with an optional signal type for routing. - /// - /// - /// - /// - void RunDirectRoute(string source, string destination, eRoutingSignalType type = eRoutingSignalType.AudioVideo); -} - -/// -/// Describes a room with matrix routing -/// -public interface IHasMatrixRouting : IHasRoutingEndpoints -{ - /// - /// The key of the matrix routing device in the room - /// - string MatrixRoutingDeviceKey { get; } -} - -/// -/// Describes a room with routing endpoints -/// -public interface IHasRoutingEndpoints -{ - /// - /// The keys of the endpoints in the room used for routing - /// - List EndpointKeys { get; } -} - -/// -/// Describes a room with a shutdown prompt timer -/// -public interface IShutdownPromptTimer -{ - /// - /// The shutdown prompt timer for the room - /// - SecondsCountdownTimer ShutdownPromptTimer { get; } - - /// - /// Sets the number of seconds for the shutdown prompt timer - /// - /// - void SetShutdownPromptSeconds(int seconds); - - /// - /// Starts the shutdown process for the room with a given shutdown type - /// - /// - void StartShutdown(eShutdownType type); -} - -/// -/// Describes a room with a tech password -/// -public interface ITechPassword -{ - /// - /// Event that fires with the result of validating a tech password - /// - event EventHandler TechPasswordValidateResult; - - /// - /// Event that fires when the tech password is changed - /// - event EventHandler TechPasswordChanged; - - /// - /// The length of the tech password - /// - int TechPasswordLength { get; } - - /// - /// Validates a given tech password against the current tech password for the room. Fires the TechPasswordValidateResult event with the result of the validation. - /// - /// - void ValidateTechPassword(string password); - - /// - /// Sets a new tech password for the room. Fires the TechPasswordChanged event when the password is successfully changed. - /// - /// - /// - void SetTechPassword(string oldPassword, string newPassword); -} - -/// -/// Event args for tech password validation results -/// -public class TechPasswordEventArgs : EventArgs -{ - /// - /// Indicates whether the tech password validation was successful or not - /// - public bool IsValid { get; private set; } - - /// - /// Constructor - /// - /// - public TechPasswordEventArgs(bool isValid) + public interface IHasInCallFeedback { - IsValid = isValid; + /// + /// Gets the InCallFeedback + /// + BoolFeedback InCallFeedback { get; } } -} - -/// -/// For rooms that default presentation only routing -/// -public interface IRunDefaultPresentRoute -{ - /// - /// Runs the default presentation route for the room. This is typically used for a "Present" button in the UI that routes a selected source to the default presentation display without needing to specify the source or destination for the route. - /// - /// True if the route was successfully run, false otherwise. - bool RunDefaultPresentRoute(); -} - -/// -/// For rooms that have default presentation and calling routes -/// -public interface IRunDefaultCallRoute : IRunDefaultPresentRoute -{ - /// - /// Runs the default call route for the room. This is typically used for a "Call" button in the UI that routes a selected source to the default call display without needing to specify the source or destination for the route. - /// - /// True if the route was successfully run, false otherwise. - bool RunDefaultCallRoute(); -} - -/// -/// Describes environmental controls available on a room such as lighting, shades, temperature, etc. -/// -public interface IEnvironmentalControls -{ - /// - /// A list of devices in the room that can be used for environmental control such as lighting, shades, temperature, etc. - /// - List EnvironmentalControlDevices { get; } /// - /// Indicates whether the room has any devices that can be used for environmental control such as lighting, shades, temperature, etc. + /// For rooms with a single display /// - bool HasEnvironmentalControlDevices { get; } -} - -/// -/// Describes a room with occupancy status -/// -public interface IRoomOccupancy:IKeyed -{ - /// - /// The occupancy status provider for the room, which provides the current occupancy status of the room based on a sensor or other input. - /// This is used for features such as automatic shutdown when the room is vacant, adjusting environmental controls based on occupancy, and other occupancy-based automation. - /// - IOccupancyStatusProvider RoomOccupancy { get; } + public interface IHasDefaultDisplay + { + /// + /// Gets the DefaultDisplay + /// + IRoutingSink DefaultDisplay { get; } + } /// - /// Indicates whether the occupancy status provider for the room is remote, meaning it is not directly connected to the control system and may require additional setup or configuration to work properly. - /// This can be used to determine whether certain features that rely on occupancy status should be enabled or disabled for the room. + /// For rooms with multiple displays /// - bool OccupancyStatusProviderIsRemote { get; } + [Obsolete("Will be removed in a future version")] + public interface IHasMultipleDisplays + { + /// + /// Gets the Displays dictionary + /// + Dictionary Displays { get; } + } /// - /// Sets the occupancy status provider for the room with a given timeout period for determining when the room is vacant. T - /// his is used to configure the occupancy-based features of the room based on the specific occupancy sensor or input being used and the desired timeout period for determining vacancy. + /// For rooms with routing /// - /// - /// - void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes); + public interface IRunRouteAction + { + /// + /// Runs a route action + /// + /// + /// + void RunRouteAction(string routeKey, string sourceListKey); + + /// + /// Runs a route action with a success callback + /// + /// + /// + /// + void RunRouteAction(string routeKey, string sourceListKey, Action successCallback); + } /// - /// Event handler for when the room is vacated for the timeout period, which can be used to trigger actions such as shutting down the room, adjusting environmental controls, or other automation based on vacancy. + /// Defines the contract for IRunDirectRouteAction /// - /// - void RoomVacatedForTimeoutPeriod(object o); + public interface IRunDirectRouteAction + { + /// + /// Runs a direct route + /// + /// + /// + /// + void RunDirectRoute(string sourceKey, string destinationKey, eRoutingSignalType type = eRoutingSignalType.AudioVideo); + } /// - /// Starts the timer for determining when the room is vacant based on the specified vacancy mode, which can be used to trigger actions such as shutting down the room, adjusting environmental controls, or other automation based on vacancy. + /// Describes a room with matrix routing /// - /// - void StartRoomVacancyTimer(eVacancyMode mode); + public interface IHasMatrixRouting + { + /// + /// Gets the MatrixRoutingDeviceKey + /// + string MatrixRoutingDeviceKey { get; } + + /// + /// Gets the EndpointKeys + /// + List EndpointKeys { get; } + } /// - /// Gets the current vacancy mode for the room, which indicates the current state of the room in terms of occupancy and can be used to determine whether certain features or automation should be enabled or disabled based on vacancy. + /// Defines the contract for IHasRoutingEndpoints /// - eVacancyMode VacancyMode { get; } + public interface IHasRoutingEndpoints + { + /// + /// Gets the EndpointKeys + /// + List EndpointKeys { get; } + } /// - /// Event that fires when the occupancy status for the room is set, which can be used to trigger actions such as updating the UI, adjusting environmental controls, or other automation based on occupancy status changes. + /// Describes a room with a shutdown prompt timer /// - event EventHandler RoomOccupancyIsSet; -} + public interface IShutdownPromptTimer + { + /// + /// Gets the ShutdownPromptTimer + /// + SecondsCountdownTimer ShutdownPromptTimer { get; } -/// -/// Describes a room with emergency features -/// -public interface IEmergency -{ - /// - /// The emergency base for the room, which provides access to emergency features such as triggering an emergency alert, contacting emergency services, or other emergency-related functionality. - /// - EssentialsRoomEmergencyBase Emergency { get; } -} + /// + /// Gets the ShutdownPromptSeconds + /// + /// number of seconds to set + void SetShutdownPromptSeconds(int seconds); -/// -/// Describes a room with a microphone privacy controller -/// -public interface IMicrophonePrivacy -{ - /// - /// The microphone privacy controller for the room, which provides access to features such as muting or unmuting microphones for privacy purposes. - /// - Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; } -} + /// + /// Starts the shutdown process + /// + /// type of shutdown event + void StartShutdown(eShutdownType type); + } -/// -/// Describes a room with a camera privacy controller -/// -public interface IHasAccessoryDevices : IKeyName -{ /// - /// A list of keys for accessory devices in the room such as cameras, microphones, or other devices that are not part of the main control system but can be integrated for additional functionality. + /// Defines the contract for ITechPassword /// - List AccessoryDeviceKeys { get; } -} + public interface ITechPassword + { + /// + /// Event fired when tech password validation result is available + /// + event EventHandler TechPasswordValidateResult; + + /// + /// Event fired when tech password is changed + /// + event EventHandler TechPasswordChanged; + + /// + /// Gets the TechPasswordLength + /// + int TechPasswordLength { get; } + + /// + /// Validates the tech password + /// + /// The tech password to validate + void ValidateTechPassword(string password); + + /// + /// Sets the tech password + /// + /// The current tech password + /// The new tech password to set + void SetTechPassword(string oldPassword, string newPassword); + } -/// -/// Describes a room wih a Cisco Navigator touchpanel -/// -public interface IHasCiscoNavigatorTouchpanel -{ /// - /// The key for the Cisco Navigator touchpanel in the room, which can be used to route calls or content to the touchpanel or for other integration purposes. + /// Represents a TechPasswordEventArgs /// - string CiscoNavigatorTouchpanelKey { get; } + public class TechPasswordEventArgs : EventArgs + { + /// + /// Gets or sets the IsValid + /// + public bool IsValid { get; private set; } + + /// + /// Constructor for TechPasswordEventArgs + /// + /// + public TechPasswordEventArgs(bool isValid) + { + IsValid = isValid; + } + } + + /// + /// Defines the contract for IRunDefaultPresentRoute + /// + public interface IRunDefaultPresentRoute + { + /// + /// Runs the default present route + /// + /// + bool RunDefaultPresentRoute(); + } + + /// + /// For rooms that have default presentation and calling routes + /// + public interface IRunDefaultCallRoute : IRunDefaultPresentRoute + { + /// + /// Runs the default call route + /// + /// + bool RunDefaultCallRoute(); + } + + /// + /// Describes environmental controls available on a room such as lighting, shades, temperature, etc. + /// + public interface IEnvironmentalControls + { + /// + /// Gets the EnvironmentalControlDevices + /// + List EnvironmentalControlDevices { get; } + + /// + /// Gets a value indicating whether the room has environmental control devices + /// + bool HasEnvironmentalControlDevices { get; } + } + + /// + /// Defines the contract for IRoomOccupancy + /// + public interface IRoomOccupancy : IKeyed + { + /// + /// Gets the RoomOccupancy + /// + IOccupancyStatusProvider RoomOccupancy { get; } + + /// + /// Gets a value indicating whether the OccupancyStatusProviderIsRemote + /// + bool OccupancyStatusProviderIsRemote { get; } + + /// + /// Sets the room occupancy + /// + /// + /// + void SetRoomOccupancy(IOccupancyStatusProvider statusProvider, int timeoutMinutes); + + /// + /// Called when the room has been vacated for the timeout period + /// + /// + void RoomVacatedForTimeoutPeriod(object o); + + /// + /// Starts the room vacancy timer + /// + /// vacancy mode + void StartRoomVacancyTimer(eVacancyMode mode); + + /// + /// Gets the VacancyMode + /// + eVacancyMode VacancyMode { get; } + + /// + /// Event fired when room occupancy is set + /// + event EventHandler RoomOccupancyIsSet; + } + + /// + /// Defines the contract for IEmergency + /// + public interface IEmergency + { + /// + /// Gets the Emergency + /// + EssentialsRoomEmergencyBase Emergency { get; } + } + + /// + /// Defines the contract for IMicrophonePrivacy + /// + public interface IMicrophonePrivacy + { + /// + /// Gets the MicrophonePrivacy + /// + Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; } + } + + /// + /// Defines the contract for IHasAccessoryDevices + /// + public interface IHasAccessoryDevices : IKeyName + { + /// + /// Gets the AccessoryDeviceKeys + /// + List AccessoryDeviceKeys { get; } + } + + /// + /// Defines the contract for IHasCiscoNavigatorTouchpanel + /// + public interface IHasCiscoNavigatorTouchpanel + { + /// + /// Gets the CiscoNavigatorTouchpanelKey + /// + string CiscoNavigatorTouchpanelKey { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/Room.cs b/src/PepperDash.Essentials.Core/Room/Room.cs index 7ce1571c..d9c51c9d 100644 --- a/src/PepperDash.Essentials.Core/Room/Room.cs +++ b/src/PepperDash.Essentials.Core/Room/Room.cs @@ -8,8 +8,8 @@ using Crestron.SimplSharpPro; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ //*************************************************************************************************** /// @@ -87,14 +87,15 @@ namespace PepperDash.Essentials.Core; { get { - return new FeedbackCollection - { - RoomIsOnFeedback, - IsCoolingDownFeedback, - IsWarmingUpFeedback - }; + return new FeedbackCollection + { + RoomIsOnFeedback, + IsCoolingDownFeedback, + IsWarmingUpFeedback + }; } } #endregion - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Room/iOccupancyStatusProvider.cs b/src/PepperDash.Essentials.Core/Room/iOccupancyStatusProvider.cs index 28dfcd26..8ea858ee 100644 --- a/src/PepperDash.Essentials.Core/Room/iOccupancyStatusProvider.cs +++ b/src/PepperDash.Essentials.Core/Room/iOccupancyStatusProvider.cs @@ -6,16 +6,16 @@ using Crestron.SimplSharp; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IOccupancyStatusProvider -/// -public interface IOccupancyStatusProvider +namespace PepperDash.Essentials.Core { /// - /// Gets the RoomIsOccupiedFeedback + /// Defines the contract for IOccupancyStatusProvider /// - BoolFeedback RoomIsOccupiedFeedback { get; } -} + public interface IOccupancyStatusProvider + { + /// + /// Gets the RoomIsOccupiedFeedback + /// + BoolFeedback RoomIsOccupiedFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs b/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs index 79854d3c..6d127622 100644 --- a/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs +++ b/src/PepperDash.Essentials.Core/Routing/DummyRoutingInputsDevice.cs @@ -1,32 +1,39 @@ -using PepperDash.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core.Routing; +using PepperDash.Core; -/// -/// Represents a DummyRoutingInputsDevice -/// -public class DummyRoutingInputsDevice : Device, IRoutingSource, IRoutingOutputs +namespace PepperDash.Essentials.Core.Routing { - /// - /// Gets or sets the AudioVideoOutputPort - /// - public RoutingOutputPort AudioVideoOutputPort { get; private set; } - - /// - /// contains the output port - /// - public RoutingPortCollection OutputPorts + /// + /// Represents a DummyRoutingInputsDevice + /// + public class DummyRoutingInputsDevice : Device, IRoutingSource, IRoutingOutputs { - get { return new RoutingPortCollection() { AudioVideoOutputPort }; } - } + /// + /// Gets or sets the AudioVideoOutputPort + /// + public RoutingOutputPort AudioVideoOutputPort { get; private set; } - /// - /// constructor - /// - /// key for special device - public DummyRoutingInputsDevice(string key) : base(key) - { - AudioVideoOutputPort = new RoutingOutputPort("internal", eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.BackplaneOnly, - null, this, true); + /// + /// contains the output port + /// + public RoutingPortCollection OutputPorts + { + get { return new RoutingPortCollection() { AudioVideoOutputPort }; } + } + + /// + /// constructor + /// + /// key for special device + public DummyRoutingInputsDevice(string key) : base(key) + { + AudioVideoOutputPort = new RoutingOutputPort("internal", eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.BackplaneOnly, + null, this, true); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/Extensions.cs b/src/PepperDash.Essentials.Core/Routing/Extensions.cs index f12f7f53..303a0194 100644 --- a/src/PepperDash.Essentials.Core/Routing/Extensions.cs +++ b/src/PepperDash.Essentials.Core/Routing/Extensions.cs @@ -4,548 +4,403 @@ using System.Diagnostics; using System.Linq; using PepperDash.Essentials.Core.Queues; using PepperDash.Essentials.Core.Routing; -using Serilog.Events; using Debug = PepperDash.Core.Debug; -namespace PepperDash.Essentials.Core; - - -/// -/// Extensions added to any IRoutingInputs classes to provide discovery-based routing -/// on those destinations. -/// -public static class Extensions +namespace PepperDash.Essentials.Core { /// - /// Stores pending route requests, keyed by the destination device key. - /// Used primarily to handle routing requests while a device is cooling down. + /// Extensions added to any IRoutingInputs classes to provide discovery-based routing + /// on those destinations. /// - private static readonly Dictionary RouteRequests = new Dictionary(); + public static class Extensions + { - - /// - /// A collection of RouteDescriptors for each signal type. - /// - public static readonly Dictionary RouteDescriptors = new Dictionary() + /// + /// A collection of RouteDescriptors for each signal type. + /// + public static readonly Dictionary RouteDescriptors = new Dictionary() { { eRoutingSignalType.Audio, new RouteDescriptorCollection() }, { eRoutingSignalType.Video, new RouteDescriptorCollection() }, - { eRoutingSignalType.AudioVideo, new RouteDescriptorCollection() } + { eRoutingSignalType.SecondaryAudio, new RouteDescriptorCollection() }, + { eRoutingSignalType.AudioVideo, new RouteDescriptorCollection() }, + { eRoutingSignalType.UsbInput, new RouteDescriptorCollection() }, + { eRoutingSignalType.UsbOutput, new RouteDescriptorCollection() } }; - /// - /// A queue to process route requests and releases sequentially. - /// - private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue"); + /// + /// Stores pending route requests, keyed by the destination device key. + /// Used primarily to handle routing requests while a device is cooling down. + /// + private static readonly Dictionary RouteRequests = new Dictionary(); - /// - /// Indexed lookup of TieLines by destination device key for faster queries. - /// - private static Dictionary> _tieLinesByDestination; + /// + /// A queue to process route requests and releases sequentially. + /// + private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue"); - /// - /// Indexed lookup of TieLines by source device key for faster queries. - /// - private static Dictionary> _tieLinesBySource; + /// + /// Indexed lookup of TieLines by destination device key for faster queries. + /// + private static Dictionary> _tieLinesByDestination; - // /// - // /// Cache of failed route attempts to avoid re-checking impossible paths. - // /// Format: "sourceKey|destKey|signalType" - // /// - // private static readonly HashSet _impossibleRoutes = new HashSet(); + /// + /// Indexed lookup of TieLines by source device key for faster queries. + /// + private static Dictionary> _tieLinesBySource; - /// - /// Indexes all TieLines by source and destination device keys for faster lookups. - /// Should be called once at system startup after all TieLines are created. - /// - public static void IndexTieLines() - { - try + /// + /// Indexes all TieLines by source and destination device keys for faster lookups. + /// Should be called once at system startup after all TieLines are created. + /// + public static void IndexTieLines() { - Debug.LogMessage(LogEventLevel.Information, "Indexing TieLines for faster route discovery"); - - _tieLinesByDestination = TieLineCollection.Default - .GroupBy(t => t.DestinationPort.ParentDevice.Key) - .ToDictionary(g => g.Key, g => g.ToList()); - - _tieLinesBySource = TieLineCollection.Default - .GroupBy(t => t.SourcePort.ParentDevice.Key) - .ToDictionary(g => g.Key, g => g.ToList()); - - Debug.LogMessage(LogEventLevel.Information, "TieLine indexing complete. {0} destination keys, {1} source keys", - null, _tieLinesByDestination.Count, _tieLinesBySource.Count); - } - catch (Exception ex) - { - Debug.LogError("Exception indexing TieLines: {exception}", ex.Message); - Debug.LogDebug(ex, "Stack Trace: "); - } - } - - /// - /// Gets TieLines connected to a destination device. - /// Uses indexed lookup if available, otherwise falls back to LINQ query. - /// - /// The destination device key - /// List of TieLines connected to the destination - private static IEnumerable GetTieLinesForDestination(string destinationKey) - { - if (_tieLinesByDestination != null && _tieLinesByDestination.TryGetValue(destinationKey, out List tieLines)) - { - return tieLines; - } - - // Fallback to LINQ if index not available - return TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destinationKey); - } - - /// - /// Gets TieLines connected to a source device. - /// Uses indexed lookup if available, otherwise falls back to LINQ query. - /// - /// The source device key - /// List of TieLines connected to the source - private static IEnumerable GetTieLinesForSource(string sourceKey) - { - if (_tieLinesBySource != null && _tieLinesBySource.TryGetValue(sourceKey, out List tieLines)) - { - return tieLines; - } - - // Fallback to LINQ if index not available - return TieLineCollection.Default.Where(t => t.SourcePort.ParentDevice.Key == sourceKey); - } - - /// - /// Creates a cache key for route impossibility tracking. - /// - /// Source device key - /// Destination device key - /// Signal type - /// Cache key string - private static string GetRouteKey(string sourceKey, string destKey, eRoutingSignalType type) - { - return string.Format("{0}|{1}|{2}", sourceKey, destKey, type); - } - - // /// - // /// Clears the impossible routes cache. Should be called if TieLines are added/removed at runtime. - // /// - // public static void ClearImpossibleRoutesCache() - // { - // _impossibleRoutes.Clear(); - // Debug.LogMessage(LogEventLevel.Information, "Impossible routes cache cleared"); - // } - - - /// - /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute - /// and then attempts a new Route and if sucessful, stores that RouteDescriptor - /// in RouteDescriptorCollection.DefaultCollection - /// - public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "") - { - // Remove this line before committing!!!!! - var frame = new StackFrame(1, true); - Debug.LogMessage(LogEventLevel.Information, "ReleaseAndMakeRoute Called from {method} with params {destinationKey}:{sourceKey}:{signalType}:{destinationPortKey}:{sourcePortKey}", frame.GetMethod().Name, destination.Key, source.Key, signalType.ToString(), destinationPortKey, sourcePortKey); - - var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey); - var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey); - - ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort); - } - - /// - /// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set - /// - /// destination to clear - public static void ReleaseRoute(this IRoutingInputs destination) - { - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, false)); - } - - /// - /// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set - /// - /// destination to clear - /// Input to use to find existing route - public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey) - { - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, false)); - } - - /// - /// Clears the route on the destination. This will remove any routes that are currently in use - /// - /// Destination - public static void ClearRoute(this IRoutingInputs destination) - { - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, true)); - } - - /// - /// Clears the route on the destination. This will remove any routes that are currently in use - /// - /// destination - /// input to use to find existing route - public static void ClearRoute(this IRoutingInputs destination, string inputPortKey) - { - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, true)); - } - - /// - /// Removes the route request for the destination. This will remove any routes that are currently in use - /// - /// destination device key - public static void RemoveRouteRequestForDestination(string destinationKey) - { - Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey); - - var result = RouteRequests.Remove(destinationKey); - - var messageTemplate = result ? "Route Request for {destination} removed" : "Route Request for {destination} not found"; - - Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey); - } - - /// - /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. - /// Routes of type AudioVideo will be built as two separate routes, audio and video. If - /// a route is discovered, a new RouteDescriptor is returned. If one or both parts - /// of an audio/video route are discovered a route descriptor is returned. If no route is - /// discovered, then null is returned - /// - public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) - { - // if it's a single signal type, find the route - if (!signalType.HasFlag(eRoutingSignalType.AudioVideo)) - { - var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, signalType); - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType); - - if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort)) - singleTypeRouteDescriptor = null; - - var routes = singleTypeRouteDescriptor?.Routes ?? new List(); - foreach (var route in routes) + try { - Debug.LogMessage(LogEventLevel.Verbose, "Route for device: {route}", destination, route.ToString()); + Debug.LogInformation("Indexing TieLines for faster route discovery"); + + _tieLinesByDestination = TieLineCollection.Default + .GroupBy(t => t.DestinationPort.ParentDevice.Key) + .ToDictionary(g => g.Key, g => g.ToList()); + + _tieLinesBySource = TieLineCollection.Default + .GroupBy(t => t.SourcePort.ParentDevice.Key) + .ToDictionary(g => g.Key, g => g.ToList()); + + Debug.LogInformation("TieLine indexing complete. {0} destination keys, {1} source keys", + _tieLinesByDestination.Count, _tieLinesBySource.Count); + } + catch (Exception ex) + { + Debug.LogError("Exception indexing TieLines: {exception}", ex.Message); + Debug.LogDebug(ex, "Stack Trace: "); + } + } + + /// + /// Gets TieLines connected to a destination device. + /// Uses indexed lookup if available, otherwise falls back to LINQ query. + /// + /// The destination device key + /// List of TieLines connected to the destination + private static IEnumerable GetTieLinesForDestination(string destinationKey) + { + if (_tieLinesByDestination != null && _tieLinesByDestination.TryGetValue(destinationKey, out List tieLines)) + { + return tieLines; } - return (singleTypeRouteDescriptor, null); - } - // otherwise, audioVideo needs to be handled as two steps. - - Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key); - - var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio); - - var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort); - - if (!audioSuccess) - Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key); - - var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video); - - var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort); - - if (!videoSuccess) - Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key); - - foreach (var route in audioRouteDescriptor.Routes) - { - Debug.LogMessage(LogEventLevel.Verbose, "Audio route for device: {route}", destination, route.ToString()); + // Fallback to LINQ if index not available + return TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destinationKey); } - foreach (var route in videoRouteDescriptor.Routes) + /// + /// Gets TieLines connected to a source device. + /// Uses indexed lookup if available, otherwise falls back to LINQ query. + /// + /// The source device key + /// List of TieLines connected to the source + private static IEnumerable GetTieLinesForSource(string sourceKey) { - Debug.LogMessage(LogEventLevel.Verbose, "Video route for device: {route}", destination, route.ToString()); - } - - - if (!audioSuccess && !videoSuccess) - return (null, null); - - // Return null for descriptors that have no routes - return (audioSuccess && audioRouteDescriptor.Routes.Count > 0 ? audioRouteDescriptor : null, - videoSuccess && videoRouteDescriptor.Routes.Count > 0 ? videoRouteDescriptor : null); - } - - - /// - /// Internal method to handle the logic for releasing an existing route and making a new one. - /// Handles devices with cooling states by queueing the request. - /// - /// The destination device. - /// The source device. - /// The type of signal to route. - /// The specific destination input port (optional). - /// The specific source output port (optional). - private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null) - { - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null"); - if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null"); - - var routeRequest = new RouteRequest - { - Destination = destination, - DestinationPort = destinationPort, - Source = source, - SourcePort = sourcePort, - SignalType = signalType - }; - - var coolingDevice = destination as IWarmingCooling; - - //We already have a route request for this device, and it's a cooling device and is cooling - if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; - - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests[destination.Key] = routeRequest; - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - - return; - } - - //New Request - if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) - { - coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; - - RouteRequests.Add(destination.Key, routeRequest); - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - return; - } - - if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) - { - var handledRequest = RouteRequests[destination.Key]; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown; - - RouteRequests.Remove(destination.Key); - - Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key); - } - - routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false)); - - routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest)); - } - - /// - /// Executes the actual routing based on a . - /// Finds the route path, adds it to the collection, and executes the switches. - /// - /// The route request details. - private static void RunRouteRequest(RouteRequest request) - { - try - { - if (request.Source == null) - return; - - RouteDescriptor audioOrSingleRoute = null; - RouteDescriptor videoRoute = null; - - // Try to use pre-loaded route descriptors first - if (request.SignalType.HasFlag(eRoutingSignalType.AudioVideo)) + if (_tieLinesBySource != null && _tieLinesBySource.TryGetValue(sourceKey, out List tieLines)) { - // For AudioVideo routes, check both Audio and Video collections - if (RouteDescriptors.TryGetValue(eRoutingSignalType.Audio, out RouteDescriptorCollection audioCollection)) + return tieLines; + } + + // Fallback to LINQ if index not available + return TieLineCollection.Default.Where(t => t.SourcePort.ParentDevice.Key == sourceKey); + } + + /// + /// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute + /// and then attempts a new Route and if sucessful, stores that RouteDescriptor + /// in RouteDescriptorCollection.DefaultCollection + /// + public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "") + { + // Remove this line before committing!!!!! + var frame = new StackFrame(1, true); + Debug.LogInformation("ReleaseAndMakeRoute Called from {method} with params {destinationKey}:{sourceKey}:{signalType}:{destinationPortKey}:{sourcePortKey}", frame.GetMethod().Name, destination.Key, source.Key, signalType.ToString(), destinationPortKey, sourcePortKey); + + var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey); + var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey); + + ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort); + } + + /// + /// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set. + /// + /// destination to clear + public static void ReleaseRoute(this IRoutingInputs destination) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, false)); + } + + /// + /// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set + /// + /// destination to clear + /// Input to use to find existing route + /// + /// ReleaseRoute method + /// + public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, false)); + } + + /// + /// Clears the route on the destination. This will remove any routes that are currently in use + /// + /// Destination + public static void ClearRoute(this IRoutingInputs destination) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, true)); + } + + /// + /// Clears the route on the destination. This will remove any routes that are currently in use + /// + /// destination + /// input to use to find existing route + /// + /// ClearRoute method + /// + public static void ClearRoute(this IRoutingInputs destination, string inputPortKey) + { + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, true)); + } + + /// + /// Removes the route request for the destination. This will remove any routes that are currently in use + /// + /// destination device key + public static void RemoveRouteRequestForDestination(string destinationKey) + { + Debug.LogInformation("Removing route request for {destination}", destinationKey); + + var result = RouteRequests.Remove(destinationKey); + + var messageTemplate = result ? "Route Request for {destination} removed" : "Route Request for {destination} not found"; + + Debug.LogInformation(messageTemplate, destinationKey); + } + + /// + /// Builds a RouteDescriptor that contains the steps necessary to make a route between devices. + /// Routes of type AudioVideo will be built as two separate routes, audio and video. If + /// a route is discovered, a new RouteDescriptor is returned. If one or both parts + /// of an audio/video route are discovered a route descriptor is returned. If no route is + /// discovered, then null is returned + /// + public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) + { + // if it's a single signal type, find the route + if (!signalType.HasFlag(eRoutingSignalType.AudioVideo) && + !(signalType.HasFlag(eRoutingSignalType.Video) && signalType.HasFlag(eRoutingSignalType.SecondaryAudio))) + { + var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, signalType); + Debug.LogDebug(destination, "Attempting to build source route from {sourceKey} of type {type}", source.Key, signalType); + + if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort)) + singleTypeRouteDescriptor = null; + + var routes = singleTypeRouteDescriptor?.Routes ?? new List(); + foreach (var route in routes) { - audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d => - d.Source.Key == request.Source.Key && - d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && - (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); + Debug.LogVerbose(destination, "Route for device: {route}", route.ToString()); } - if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection)) - { - videoRoute = videoCollection.Descriptors.FirstOrDefault(d => - d.Source.Key == request.Source.Key && - d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && - (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); - } + return (singleTypeRouteDescriptor, null); + } + // otherwise, audioVideo needs to be handled as two steps. + + Debug.LogDebug(destination, "Attempting to build source route from {destinationKey} to {sourceKey} of type {type}", destination.Key, source.Key, signalType); + + RouteDescriptor audioRouteDescriptor; + + if (signalType.HasFlag(eRoutingSignalType.SecondaryAudio)) + { + audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.SecondaryAudio); } else { - // For single signal type routes - if (RouteDescriptors.TryGetValue(request.SignalType, out RouteDescriptorCollection collection)) - { - audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d => - d.Source.Key == request.Source.Key && - d.Destination.Key == request.Destination.Key && - (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && - (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); - } + audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Audio); } - // If no pre-loaded route found, build it dynamically - if (audioOrSingleRoute == null && videoRoute == null) + var audioSuccess = destination.GetRouteToSource(source, null, null, signalType.HasFlag(eRoutingSignalType.SecondaryAudio) ? eRoutingSignalType.SecondaryAudio : eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort); + + if (!audioSuccess) + Debug.LogDebug(destination, "Cannot find audio route to {0}", source.Key); + + var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, sourcePort, eRoutingSignalType.Video); + + var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort); + + if (!videoSuccess) + Debug.LogDebug(destination, "Cannot find video route to {0}", source.Key); + + foreach (var route in audioRouteDescriptor.Routes) { - Debug.LogMessage(LogEventLevel.Debug, "No pre-loaded route found, building dynamically", request.Destination); - (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort); + Debug.LogVerbose(destination, "Audio route for device: {route}", route.ToString()); + } + + foreach (var route in videoRouteDescriptor.Routes) + { + Debug.LogVerbose(destination, "Video route for device: {route}", route.ToString()); } - if (audioOrSingleRoute == null && videoRoute == null) + if (!audioSuccess && !videoSuccess) + return (null, null); + + // Return null for descriptors that have no routes + return (audioSuccess && audioRouteDescriptor.Routes.Count > 0 ? audioRouteDescriptor : null, + videoSuccess && videoRouteDescriptor.Routes.Count > 0 ? videoRouteDescriptor : null); + } + + /// + /// Internal method to handle the logic for releasing an existing route and making a new one. + /// Handles devices with cooling states by queueing the request. + /// + /// The destination device. + /// The source device. + /// The type of signal to route. + /// The specific destination input port (optional). + /// The specific source output port (optional). + private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destinationPort == null) Debug.LogDebug("Destination port is null"); + if (sourcePort == null) Debug.LogDebug("Source port is null"); + + var routeRequest = new RouteRequest + { + Destination = destination, + DestinationPort = destinationPort, + Source = source, + SourcePort = sourcePort, + SignalType = signalType + }; + + var coolingDevice = destination as IWarmingCooling; + + //We already have a route request for this device, and it's a cooling device and is cooling + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) + { + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown; + + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests[destination.Key] = routeRequest; + + Debug.LogInformation("Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", destination.Key, routeRequest.Source.Key); + return; - - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute); - - if (videoRoute != null) - { - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute); } - Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination); - - audioOrSingleRoute.ExecuteRoutes(); - videoRoute?.ExecuteRoutes(); - - // Update ICurrentSources on the destination if it implements the interface - if (request.Destination is ICurrentSources currentSourcesDevice && request.Source is IRoutingSource routingSource) + //New Request + if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true) { - if (request.SignalType.HasFlag(eRoutingSignalType.Audio) || request.SignalType.HasFlag(eRoutingSignalType.AudioVideo)) + coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown; + + RouteRequests.Add(destination.Key, routeRequest); + + Debug.LogInformation("Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", destination.Key, routeRequest.Source.Key); + return; + } + + if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false) + { + var handledRequest = RouteRequests[destination.Key]; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown; + + RouteRequests.Remove(destination.Key); + + Debug.LogInformation("Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", destination.Key, routeRequest.Source.Key); + } + + routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, destinationPort?.Key ?? string.Empty, false)); + + routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest)); + } + + /// + /// Maps destination input ports to source output ports for all routing devices. + /// + public static void MapDestinationsToSources() + { + try + { + // Index TieLines before mapping if not already done + if (_tieLinesByDestination == null || _tieLinesBySource == null) { - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Audio, routingSource); + IndexTieLines(); } - if (request.SignalType.HasFlag(eRoutingSignalType.Video) || request.SignalType.HasFlag(eRoutingSignalType.AudioVideo)) + var sinks = DeviceManager.AllDevices.OfType() + .Where(d => !(d is IRoutingInputsOutputs)).ToList(); + var sources = DeviceManager.AllDevices.OfType() + .Where(d => !(d is IRoutingInputsOutputs)).ToList(); + + foreach (var sink in sinks) { - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Video, routingSource); - } - } - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request); - } - } - - /// - /// Will release the existing route on the destination, if it is found in RouteDescriptorCollection.DefaultCollection - /// - /// - /// The input port key to use to find the route. If empty, will use the first available input port - /// If true, will clear the route on the destination. This will remove any routes that are currently in use - private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey, bool clearRoute) - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - - if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) - { - var coolingDevice = destination as IWarmingCooling; - - coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; - } - - RouteRequests.Remove(destination.Key); - - var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey); - if (current != null) - { - Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key); - current.ReleaseRoutes(clearRoute); - } - - // Clear ICurrentSources on the destination if clearing the route - if (clearRoute && destination is ICurrentSources currentSourcesDevice) - { - if (current != null) - { - var signalType = current.SignalType; - - if (signalType.HasFlag(eRoutingSignalType.Audio) || signalType.HasFlag(eRoutingSignalType.AudioVideo)) + foreach (var source in sources) { - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Audio, null); - } - - if (signalType.HasFlag(eRoutingSignalType.Video) || signalType.HasFlag(eRoutingSignalType.AudioVideo)) - { - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Video, null); - } - } - else - { - // No route descriptor found, clear all signal types - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Audio, null); - currentSourcesDevice.SetCurrentSource(eRoutingSignalType.Video, null); - } - } - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'", null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - } - } - - /// - /// Maps destination input ports to source output ports for all routing devices. - /// - public static void MapDestinationsToSources() - { - try - { - // Index TieLines before mapping if not already done - if (_tieLinesByDestination == null || _tieLinesBySource == null) - { - IndexTieLines(); - } - - var sinks = DeviceManager.AllDevices.OfType().Where(d => !(d is IRoutingMidpoint)); - var sources = DeviceManager.AllDevices.OfType().Where(d => !(d is IRoutingMidpoint)); - - foreach (var sink in sinks) - { - foreach (var source in sources) - { - foreach (var inputPort in sink.InputPorts) - { - foreach (var outputPort in source.OutputPorts) + foreach (var inputPort in sink.InputPorts) { - var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, inputPort.Type, inputPort, outputPort); - - if (audioOrSingleRoute == null && videoRoute == null) + foreach (var outputPort in source.OutputPorts) { - continue; - } + var (audioOrSingleRoute, videoRoute) = sink.GetRouteToSource(source, outputPort.Type, inputPort, outputPort); - if (audioOrSingleRoute != null) - { - // Only add routes that have actual switching steps - if (audioOrSingleRoute.Routes == null || audioOrSingleRoute.Routes.Count == 0) + if (audioOrSingleRoute == null && videoRoute == null) { continue; } - // Add to the appropriate collection(s) based on signal type - // Note: A single route descriptor with combined flags (e.g., AudioVideo) will be added once per matching signal type - if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Audio)) + Debug.LogVerbose("AudioOrSingleRoute Found: {audioRoute}", audioOrSingleRoute); + + Debug.LogVerbose("VideoRoute Found: {videoRoute}", videoRoute); + + if (audioOrSingleRoute != null) { - RouteDescriptors[eRoutingSignalType.Audio].AddRouteDescriptor(audioOrSingleRoute); - } - if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Video)) - { - RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(audioOrSingleRoute); - } - if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Usb)) - { - RouteDescriptors[eRoutingSignalType.Usb].AddRouteDescriptor(audioOrSingleRoute); + // Only add routes that have actual switching steps + if (audioOrSingleRoute.Routes == null || audioOrSingleRoute.Routes.Count == 0) + { + continue; + } + + Debug.LogVerbose("Route mapped: {source} -> {sink} via {input}/{output}, type {type}", + source.Key, sink.Key, + inputPort.Key, outputPort.Key, audioOrSingleRoute.SignalType); + + // Add to the appropriate collection(s) based on signal type + // Note: A single route descriptor with combined flags (e.g., AudioVideo) will be added once per matching signal type + if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Audio)) + { + RouteDescriptors[eRoutingSignalType.Audio].AddRouteDescriptor(audioOrSingleRoute); + } + if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.Video)) + { + RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(audioOrSingleRoute); + } + if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio)) + { + RouteDescriptors[eRoutingSignalType.SecondaryAudio].AddRouteDescriptor(audioOrSingleRoute); + } + if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbInput)) + { + RouteDescriptors[eRoutingSignalType.UsbInput].AddRouteDescriptor(audioOrSingleRoute); + } + if (audioOrSingleRoute.SignalType.HasFlag(eRoutingSignalType.UsbOutput)) + { + RouteDescriptors[eRoutingSignalType.UsbOutput].AddRouteDescriptor(audioOrSingleRoute); + } } if (videoRoute != null) { @@ -555,6 +410,10 @@ public static class Extensions continue; } + Debug.LogVerbose("Video route mapped: {source} -> {sink} via {input}/{output}", + source.Key, sink.Key, + inputPort.Key, outputPort.Key); + RouteDescriptors[eRoutingSignalType.Video].AddRouteDescriptor(videoRoute); } } @@ -562,157 +421,269 @@ public static class Extensions } } } - } - catch (Exception ex) - { - Debug.LogError("Exception mapping routes: {exception}", ex.Message); - Debug.LogDebug(ex, "Stack Trace: "); - } - } - - /// - /// The recursive part of this. Will stop on each device, search its inputs for the - /// desired source and if not found, invoke this function for the each input port - /// hoping to find the source. - /// - /// - /// - /// The RoutingInputPort whose link is being checked for a route - /// The RoutingOutputPort whose link is being checked for a route - /// The RoutingOutputPort whose link is being checked for a route - /// Prevents Devices from being twice-checked - /// This recursive function should not be called with AudioVideo - /// Just an informational counter - /// The RouteDescriptor being populated as the route is discovered - /// true if source is hit - private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, - RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, - eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) - { - cycle++; - - // var routeKey = GetRouteKey(source.Key, destination.Key, signalType); - // if (_impossibleRoutes.Contains(routeKey)) - // { - // Debug.LogMessage(LogEventLevel.Verbose, "Route {0} is cached as impossible, skipping", null, routeKey); - // return false; - // } - - Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString()); - - RoutingInputPort goodInputPort = null; - - // Use indexed lookup instead of LINQ query - var allDestinationTieLines = GetTieLinesForDestination(destination.Key); - - IEnumerable destinationTieLines; - TieLine directTie = null; - - if (destinationPort == null) - { - destinationTieLines = allDestinationTieLines.Where(t => - t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo); - } - else - { - destinationTieLines = allDestinationTieLines.Where(t => - t.DestinationPort.Key == destinationPort.Key && t.Type.HasFlag(signalType)); - } - - // find the TieLine without a port - if (destinationPort == null && sourcePort == null) - { - directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key); - } - // find a tieLine to a specific destination port without a specific source port - else if (destinationPort != null && sourcePort == null) - { - directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key); - } - // find a tieline to a specific source port without a specific destination port - else if (destinationPort == null & sourcePort != null) - { - directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key); - } - // find a tieline to a specific source port and destination port - else if (destinationPort != null && sourcePort != null) - { - directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key); - } - - if (directTie != null) // Found a tie directly to the source - { - goodInputPort = directTie.DestinationPort; - } - else // no direct-connect. Walk back devices. - { - Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {sourceKey}. Walking down tie lines", destination, source.Key); - - // No direct tie? Run back out on the inputs' attached devices... - // Only the ones that are routing devices - var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingMidpoint); - - //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration - if (alreadyCheckedDevices == null) - alreadyCheckedDevices = new List(); - alreadyCheckedDevices.Add(destination as IRoutingMidpoint); - - foreach (var tieLine in midpointTieLines) + catch (Exception ex) { - var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingMidpoint; - - // Check if this previous device has already been walked - if (alreadyCheckedDevices.Contains(midpointDevice)) - { - Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", destination, midpointDevice.Key, destination.Key); - continue; - } - - var midpointOutputPort = tieLine.SourcePort; - - Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {midpointDeviceKey}", destination, midpointDevice.Key); - - // haven't seen this device yet. Do it. Pass the output port to the next - // level to enable switching on success - var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort, - alreadyCheckedDevices, signalType, cycle, routeTable, null, sourcePort); - - if (upstreamRoutingSuccess) - { - Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination); - Debug.LogMessage(LogEventLevel.Verbose, "Route found on {midpointDeviceKey}", destination, midpointDevice.Key); - Debug.LogMessage(LogEventLevel.Verbose, "TieLine: SourcePort: {SourcePort} DestinationPort: {DestinationPort}", destination, tieLine.SourcePort, tieLine.DestinationPort); - goodInputPort = tieLine.DestinationPort; - break; // Stop looping the inputs in this cycle - } + Debug.LogError("Exception mapping routes: {exception}", ex.Message); + Debug.LogDebug(ex, "Stack Trace: "); } } - - if (goodInputPort == null) + /// + /// Executes the actual routing based on a . + /// Finds the route path, adds it to the collection, and executes the switches. + /// + /// The route request details. + private static void RunRouteRequest(RouteRequest request) { - Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key); + try + { + if (request.Source == null) + return; - // // Cache this as an impossible route - // _impossibleRoutes.Add(routeKey); + RouteDescriptor audioOrSingleRoute = null; + RouteDescriptor videoRoute = null; - return false; + // Try to use pre-loaded route descriptors first + if (request.SignalType.HasFlag(eRoutingSignalType.AudioVideo)) + { + // For AudioVideo routes, check both Audio and Video collections + if (RouteDescriptors.TryGetValue(eRoutingSignalType.Audio, out RouteDescriptorCollection audioCollection)) + { + audioOrSingleRoute = audioCollection.Descriptors.FirstOrDefault(d => + d.Source.Key == request.Source.Key && + d.Destination.Key == request.Destination.Key && + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); + } + + if (RouteDescriptors.TryGetValue(eRoutingSignalType.Video, out RouteDescriptorCollection videoCollection)) + { + videoRoute = videoCollection.Descriptors.FirstOrDefault(d => + d.Source.Key == request.Source.Key && + d.Destination.Key == request.Destination.Key && + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); + } + } + else + { + // For single signal type routes + var signalTypeToCheck = request.SignalType.HasFlag(eRoutingSignalType.SecondaryAudio) + ? eRoutingSignalType.SecondaryAudio + : request.SignalType; + + if (RouteDescriptors.TryGetValue(signalTypeToCheck, out RouteDescriptorCollection collection)) + { + audioOrSingleRoute = collection.Descriptors.FirstOrDefault(d => + d.Source.Key == request.Source.Key && + d.Destination.Key == request.Destination.Key && + (request.DestinationPort == null || d.InputPort?.Key == request.DestinationPort.Key) && + (request.SourcePort == null || d.OutputPort?.Key == request.SourcePort.Key)); + } + } + + // If no pre-loaded route found, build it dynamically + if (audioOrSingleRoute == null && videoRoute == null) + { + Debug.LogDebug(request.Destination, "No pre-loaded route found, building dynamically"); + (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort); + } + + if (audioOrSingleRoute == null && videoRoute == null) + return; + + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute); + + if (videoRoute != null) + { + RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute); + } + + Debug.LogVerbose(request.Destination, "Executing full route"); + + audioOrSingleRoute.ExecuteRoutes(); + videoRoute?.ExecuteRoutes(); + } + catch (Exception ex) + { + Debug.LogError("Exception Running Route Request {request}: {exception}", request, ex.Message); + Debug.LogDebug(ex, "Stack Trace: "); + } } - // we have a route on corresponding inputPort. *** Do the route *** - - if (destination is IRoutingSinkWithFeedback) + /// + /// Will release the existing route on the destination, if it is found in RouteDescriptorCollection.DefaultCollection + /// + /// + /// The input port key to use to find the route. If empty, will use the first available input port + /// If true, will clear the route on the destination. This will remove any routes that are currently in use + private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey, bool clearRoute) { - // it's a sink device - routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); - } - else if (destination is IRoutingMidpointWithFeedback) - { - routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); - } - else // device is merely IRoutingInputOutputs - Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination); + try + { + Debug.LogInformation(destination, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - return true; + if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling) + { + var coolingDevice = destination as IWarmingCooling; + + coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown; + } + + RouteRequests.Remove(destination.Key); + + var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey); + if (current != null) + { + Debug.LogInformation(destination, "Releasing current route: {0}", current.Source.Key); + current.ReleaseRoutes(clearRoute); + } + } + catch (Exception ex) + { + Debug.LogError("Exception releasing route for '{destination}':'{inputPortKey}': {exception}", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey, ex.Message); + Debug.LogDebug(ex, "Stack Trace: "); + } + } + + /// + /// The recursive part of this. Will stop on each device, search its inputs for the + /// desired source and if not found, invoke this function for the each input port + /// hoping to find the source. + /// + /// + /// + /// The RoutingOutputPort whose link is being checked for a route + /// Prevents Devices from being twice-checked + /// This recursive function should not be called with AudioVideo + /// Just an informational counter + /// The RouteDescriptor being populated as the route is discovered + /// The RoutingOutputPort whose link is being checked for a route + /// The source output port (optional) + /// true if source is hit + private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, + RoutingOutputPort outputPortToUse, List alreadyCheckedDevices, + eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort, RoutingOutputPort sourcePort) + { + cycle++; + + Debug.LogVerbose("GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString()); + + RoutingInputPort goodInputPort = null; + + // Use indexed lookup instead of LINQ query + var allDestinationTieLines = GetTieLinesForDestination(destination.Key); + + IEnumerable destinationTieLines; + TieLine directTie = null; + + if (destinationPort == null) + { + destinationTieLines = allDestinationTieLines.Where(t => + t.Type.HasFlag(signalType) || signalType == eRoutingSignalType.AudioVideo); + } + else + { + destinationTieLines = allDestinationTieLines.Where(t => + t.DestinationPort.Key == destinationPort.Key && t.Type.HasFlag(signalType)); + } + + // find the TieLine without a port + if (destinationPort == null && sourcePort == null) + { + directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key); + } + // find a tieLine to a specific destination port without a specific source port + else if (destinationPort != null && sourcePort == null) + { + directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key); + } + // find a tieline to a specific source port without a specific destination port + else if (destinationPort == null & sourcePort != null) + { + directTie = destinationTieLines.FirstOrDefault(t => t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key); + } + // find a tieline to a specific source port and destination port + else if (destinationPort != null && sourcePort != null) + { + directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key); + } + + if (directTie != null) // Found a tie directly to the source + { + goodInputPort = directTie.DestinationPort; + } + else // no direct-connect. Walk back devices. + { + Debug.LogVerbose(destination, "is not directly connected to {sourceKey}. Walking down tie lines", source.Key); + + // No direct tie? Run back out on the inputs' attached devices... + // Only the ones that are routing devices + var midpointTieLines = destinationTieLines + .Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs) + .ToList(); + + Debug.LogVerbose(destination, "Found {tieLineCount} tie lines to walk for {destinationKey}", midpointTieLines.Count, destination.Key); + + //Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration + if (alreadyCheckedDevices == null) + alreadyCheckedDevices = new List(); + alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs); + + foreach (var tieLine in midpointTieLines) + { + var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs; + + // Check if this previous device has already been walked + if (alreadyCheckedDevices.Contains(midpointDevice)) + { + Debug.LogVerbose(destination, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", midpointDevice.Key, destination.Key); + continue; + } + + var midpointOutputPort = tieLine.SourcePort; + + Debug.LogVerbose(destination, "Trying to find route on {midpointDeviceKey}", midpointDevice.Key); + + // haven't seen this device yet. Do it. Pass the output port to the next + // level to enable switching on success + var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort, + alreadyCheckedDevices, signalType, cycle, routeTable, null, sourcePort); + + if (upstreamRoutingSuccess) + { + Debug.LogVerbose(destination, "Upstream device route found"); + Debug.LogVerbose(destination, "Route found on {midpointDeviceKey}", midpointDevice.Key); + Debug.LogVerbose(destination, "TieLine: SourcePort: {SourcePort} DestinationPort: {DestinationPort}", tieLine.SourcePort, tieLine.DestinationPort); + goodInputPort = tieLine.DestinationPort; + break; // Stop looping the inputs in this cycle + } + } + } + + + if (goodInputPort == null) + { + Debug.LogVerbose(destination, "No route found to {0} from destination {1} for type {2}", source.Key, destination.Key, signalType); + + return false; + } + + // we have a route on corresponding inputPort. *** Do the route *** + + if (destination is IRoutingSink) + { + // it's a sink device + routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort)); + } + else if (destination is IRouting) + { + routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort)); + } + else // device is merely IRoutingInputOutputs + Debug.LogVerbose(destination, "No routing. Passthrough device"); + + return true; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ICurrentSources.cs b/src/PepperDash.Essentials.Core/Routing/ICurrentSources.cs index 4222bb4d..a8940d08 100644 --- a/src/PepperDash.Essentials.Core/Routing/ICurrentSources.cs +++ b/src/PepperDash.Essentials.Core/Routing/ICurrentSources.cs @@ -6,7 +6,6 @@ namespace PepperDash.Essentials.Core.Routing /// /// The current sources for the room, keyed by eRoutingSignalType. /// This allows for multiple sources to be tracked, such as audio and video. - /// Intended to be implemented on a DestinationListItem to provide access to the current sources for the room in its context. /// /// /// This interface is used to provide access to the current sources in a room, @@ -18,7 +17,7 @@ namespace PepperDash.Essentials.Core.Routing /// Gets the current sources for the room, keyed by eRoutingSignalType. /// This dictionary contains the current source for each signal type, such as audio, video, and control signals. /// - Dictionary CurrentSources { get; } + Dictionary CurrentSources { get; } /// /// Gets the current source keys for the room, keyed by eRoutingSignalType. @@ -29,52 +28,16 @@ namespace PepperDash.Essentials.Core.Routing /// /// Event raised when the current sources change. /// - event EventHandler CurrentSourcesChanged; + event EventHandler CurrentSourcesChanged; /// /// Sets the current source for a specific signal type. /// This method updates the current source for the specified signal type and notifies any subscribers of the change. /// /// The signal type to update. - /// - /// The source device to set as the current source. - void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice); - + /// The key for the source list. + /// The source list item to set as the current source. + void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem); } - - - /// - /// Event arguments for the CurrentSourcesChanged event, providing details about the signal type and the previous and new sources. - /// - public class CurrentSourcesChangedEventArgs : EventArgs - { - /// - /// Gets the signal type for which the current source has changed. - /// - public eRoutingSignalType SignalType { get; } - - /// - /// Gets the previous source for the signal type before the change occurred. - /// - public IRoutingSource PreviousSource { get; } - - /// - /// Gets the new source for the signal type after the change occurred. - /// - public IRoutingSource NewSource { get; } - - /// - /// Initializes a new instance of the CurrentSourcesChangedEventArgs class with the specified signal type, previous source, and new source. - /// - /// The signal type for which the current source has changed. - /// The previous source for the signal type before the change occurred. - /// The new source for the signal type after the change occurred. - public CurrentSourcesChangedEventArgs(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource newSource) - { - SignalType = signalType; - PreviousSource = previousSource; - NewSource = newSource; - } - } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs new file mode 100644 index 00000000..a9e13a07 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IHasCurrentSourceInfoChange.cs @@ -0,0 +1,46 @@ +/* Unmerged change from project 'PepperDash.Essentials.Core (net6)' +Before: +namespace PepperDash.Essentials.Core.Routing.Interfaces +After: +using PepperDash; +using PepperDash.Essentials; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing.Interfaces +*/ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Delegate for SourceInfoChangeHandler + /// + public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type); + //******************************************************************************************* + // Interfaces + + /// + /// For rooms with a single presentation source, change event + /// + [Obsolete("Use ICurrentSources instead")] + public interface IHasCurrentSourceInfoChange + { + /// + /// The key for the current source info, used to look up the source in the SourceList + /// + string CurrentSourceInfoKey { get; set; } + + /// + /// The current source info for the room, used to look up the source in the SourceList + /// + SourceListItem CurrentSourceInfo { get; set; } + + /// + /// Event that is raised when the current source info changes. + /// This is used to notify the system of changes to the current source info. + /// The event handler receives the new source info and the type of change that occurred. + /// + event SourceInfoChangeHandler CurrentSourceChange; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs b/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs deleted file mode 100644 index 84b5983e..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IHasNamedRoutingSlots.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using PepperDash.Core; - -namespace PepperDash.Essentials.Core; - -/// -/// Describes a single named routing slot (input or output) on a device implementing -/// . -/// -public interface IRoutingSlotInfo : IKeyName -{ - /// Matrix slot number. - int SlotNumber { get; } - - /// Signal types this slot can carry. - eRoutingSignalType SupportedSignalTypes { get; } -} - -/// -/// Describes a named output slot, adding per-signal-type current-route feedback (keyed by the -/// routed input slot's key) that does not -/// carry. -/// -public interface IRoutingOutputSlotInfo : IRoutingSlotInfo -{ - /// The key of the input slot currently routed to this output, per signal type. - IReadOnlyDictionary CurrentRouteInputKeys { get; } - - /// Raised when the routed input on this output changes. - event EventHandler OutputSlotChanged; -} - -/// -/// Optional extension to for matrix-style routing -/// devices that track named input/output slots (with per-signal-type routing feedback) internally, -/// e.g. plugin-local slot abstractions that replaced the removed core IRoutingInputSlot/ -/// IRoutingOutputSlot during the v3 routing refactor. Devices implementing this get a richer -/// mobile-control matrix routing message (names + per-signal-type feedback) instead of the bare -/// key-only ports from alone. -/// -public interface IHasNamedRoutingSlots : IRoutingMidpointWithFeedback -{ - /// Named input slots, keyed by slot key. - IReadOnlyDictionary InputSlots { get; } - - /// Named output slots, keyed by slot key. - IReadOnlyDictionary OutputSlots { get; } -} diff --git a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs index 4898702f..50f2b5fb 100644 --- a/src/PepperDash.Essentials.Core/Routing/IInputSync.cs +++ b/src/PepperDash.Essentials.Core/Routing/IInputSync.cs @@ -1,5 +1,9 @@ using PepperDash.Core; using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace PepperDash.Essentials.Core.Routing { diff --git a/src/PepperDash.Essentials.Core/Routing/IMatrixRouting.cs b/src/PepperDash.Essentials.Core/Routing/IMatrixRouting.cs new file mode 100644 index 00000000..d12e704e --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IMatrixRouting.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// Defines the contract for IMatrixRouting + /// + public interface IMatrixRouting + { + /// + /// Gets the input slots + /// + Dictionary InputSlots { get; } + + /// + /// Gets the output slots + /// + Dictionary OutputSlots { get; } + + /// + /// Routes the specified input slot to the specified output slot for the specified signal type + /// + /// key of the input slot + /// key of the output slot + /// signal type + void Route(string inputSlotKey, string outputSlotKey, eRoutingSignalType type); + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs new file mode 100644 index 00000000..f2837350 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs @@ -0,0 +1,13 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRmcRouting + /// + public interface IRmcRouting : IRoutingNumeric + { + /// + /// Feedback for the current Audio/Video source as a number + /// + IntFeedback AudioVideoSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs new file mode 100644 index 00000000..ab14d066 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRmcRoutingWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRmcRoutingWithFeedback + /// + public interface IRmcRoutingWithFeedback : IRmcRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRouting.cs b/src/PepperDash.Essentials.Core/Routing/IRouting.cs new file mode 100644 index 00000000..64bcd7a5 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRouting.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines a midpoint device as have internal routing. Any devices in the middle of the + /// signal chain, that do switching, must implement this for routing to work otherwise + /// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough + /// device. + /// + public interface IRouting : IRoutingInputsOutputs + { + /// + /// Executes a switch on the device + /// + /// input selector + /// output selector + /// type of signal + void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); + } + + /*public interface IRouting : IRoutingInputsOutputs + { + void ExecuteSwitch(TInputSelector inputSelector, TOutputSelector outputSelector, eRoutingSignalType signalType); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs new file mode 100644 index 00000000..253ab8ee --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs @@ -0,0 +1,19 @@ +using System; + +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingFeedback + /// + public interface IRoutingFeedback : IKeyName + { + /// + /// Event raised when a numeric switch changes + /// + event EventHandler NumericSwitchChange; + //void OnSwitchChange(RoutingNumericEventArgs e); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs index 10516aee..a2b4c54e 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingHasVideoInputSyncFeedbacks.cs @@ -9,8 +9,8 @@ using Crestron.SimplSharpPro.DM; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines the contract for IRoutingHasVideoInputSyncFeedbacks @@ -21,4 +21,5 @@ namespace PepperDash.Essentials.Core; /// Video Input Sync Feedbacks /// FeedbackCollection VideoInputSyncFeedbacks { get; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputSlot.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputSlot.cs new file mode 100644 index 00000000..f620f8bf --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputSlot.cs @@ -0,0 +1,13 @@ +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// Defines the contract for IRoutingInputSlot + /// + public interface IRoutingInputSlot: IRoutingSlot, IOnline, IVideoSync + { + /// + /// Gets the Tx device key + /// + string TxDeviceKey { get; } + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs index 1b285e84..9543bb54 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs @@ -1,14 +1,21 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core; -/// -/// Defines the contract for IRoutingInputs -/// -public interface IRoutingInputs : IKeyed + +namespace PepperDash.Essentials.Core { /// - /// Collection of Input Ports + /// Defines the contract for IRoutingInputs /// - RoutingPortCollection InputPorts { get; } -} + public interface IRoutingInputs : IKeyed + { + /// + /// Collection of Input Ports + /// + RoutingPortCollection InputPorts { get; } + } +/* public interface IRoutingInputs : IKeyed + { + RoutingPortCollection, TSelector> InputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs new file mode 100644 index 00000000..89e4d031 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingInputsOutputs.cs @@ -0,0 +1,16 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingInputsOutputs + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + } + +/* /// + /// For devices like RMCs, baluns, other devices with no switching. + /// + public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs + { + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingMidpoint.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingMidpoint.cs deleted file mode 100644 index e1a01521..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingMidpoint.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PepperDash.Essentials.Core; - -/// -/// Defines a midpoint (passthrough) device that has both input and output routing ports -/// but does not perform active switching. For switching midpoints, see . -/// -public interface IRoutingMidpoint : IRoutingInputs, IRoutingOutputs -{ -} - diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingMidpointWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingMidpointWithFeedback.cs deleted file mode 100644 index 6c137c96..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingMidpointWithFeedback.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using System; - -namespace PepperDash.Essentials.Core; - -/// -/// Delegate for RouteChangedEventHandler. -/// -/// The routing device where the change occurred. -/// A descriptor of the new route that was established. -public delegate void RouteChangedEventHandler(IRoutingMidpointWithFeedback midpoint, RouteSwitchDescriptor newRoute); - -/// -/// Defines a midpoint device that performs active switching and provides feedback about its current routes. -/// Combines the capabilities of the former IRouting, IRoutingWithFeedback, and IRoutingWithClear interfaces. -/// -public interface IRoutingMidpointWithFeedback : IRoutingMidpoint -{ - /// - /// Executes a switch on the device. - /// - /// Input selector. - /// Output selector. - /// Type of signal. - void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType); - - /// - /// Clears a route to an output. - /// - /// Output to clear. - /// Signal type to clear. - void ClearRoute(object outputSelector, eRoutingSignalType signalType); - - /// - /// Gets a list describing the currently active routes on this device. - /// - List CurrentRoutes { get; } - - /// - /// Event triggered when a route changes on this device. - /// - event RouteChangedEventHandler RouteChanged; -} diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs new file mode 100644 index 00000000..cd91daa0 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumeric.cs @@ -0,0 +1,16 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingNumeric + /// + public interface IRoutingNumeric : IRouting + { + /// + /// Executes a numeric switch on the device + /// + /// input selector + /// output selector + /// type of signal + void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs new file mode 100644 index 00000000..33a837a8 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingNumericWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingNumericWithFeedback + /// + public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingOutputSlot.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputSlot.cs new file mode 100644 index 00000000..266e0724 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputSlot.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// Defines the contract for IRoutingOutputSlot + /// + public interface IRoutingOutputSlot : IRoutingSlot + { + /// + /// Event raised when output slot changes + /// + event EventHandler OutputSlotChanged; + + /// + /// Gets the Rx device key + /// + string RxDeviceKey { get; } + + /// + /// Gets the current routes + /// + Dictionary CurrentRoutes { get; } + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs index 702a2367..c26e7b70 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs @@ -1,18 +1,22 @@ -using Newtonsoft.Json; -using PepperDash.Core; +using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Defines the contract for IRoutingOutputs -/// -public interface IRoutingOutputs : IKeyed +namespace PepperDash.Essentials.Core { - /// - /// Collection of Output Ports - /// - [JsonProperty("outputPorts")] - RoutingPortCollection OutputPorts { get; } -} + /// + /// Defines the contract for IRoutingOutputs + /// + public interface IRoutingOutputs : IKeyed + { + /// + /// Collection of Output Ports + /// + RoutingPortCollection OutputPorts { get; } + } + +/* public interface IRoutingOutputs : IKeyed + { + RoutingPortCollection, TSelector> OutputPorts { get; } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs new file mode 100644 index 00000000..6e599f29 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs @@ -0,0 +1,29 @@ +using PepperDash.Essentials.Core.Routing; + +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingSink + /// + public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange + { + } + + /// + /// For fixed-source endpoint devices with an input port + /// + public interface IRoutingSinkWithInputPort : IRoutingSink + { + /// + /// Gets the current input port for this routing sink. + /// + RoutingInputPort CurrentInputPort { get; } + } + + /// + /// Interface for routing sinks that have access to the current source information. + /// + public interface IRoutingSinkWithCurrentSources : IRoutingSink, ICurrentSources + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs index b6f578c7..c0fd03df 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithFeedback.cs @@ -1,38 +1,25 @@ -using PepperDash.Core; -using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Routing; +using System; +using System.Collections.Generic; -namespace PepperDash.Essentials.Core; - -/// -/// Delegate for InputChangedEventHandler. -/// -/// The sink device that changed input. -/// The new input port selected on the sink device. -public delegate void InputChangedEventHandler(IRoutingSinkWithFeedback destination, RoutingInputPort currentPort); - -/// -/// Defines a routing sink (endpoint) device that can switch inputs and provides feedback. -/// Consolidates the former IRoutingSink, IRoutingSinkWithInputPort, IRoutingSinkWithSwitching, -/// IRoutingSinkWithSwitchingWithInputPort, and IRoutingSinkWithFeedback interfaces. -/// -public interface IRoutingSinkWithFeedback : IRoutingInputs, IKeyName, ICurrentSources +namespace PepperDash.Essentials.Core { + /// - /// Executes a switch on the device. + /// Defines the contract for IRoutingSinkWithFeedback /// - /// Input selector. - void ExecuteSwitch(object inputSelector); + public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching + { + + } - /// - /// Gets the current input port for this routing sink. +/* /// + /// For fixed-source endpoint devices /// - RoutingInputPort CurrentInputPort { get; } - - /// - /// Event raised when the input changes. - /// - event InputChangedEventHandler InputChanged; -} - - + public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching + { + RouteSwitchDescriptor CurrentRoute { get; } + event EventHandler InputChanged; + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayoutState.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayoutState.cs deleted file mode 100644 index a0ac9bec..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayoutState.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace PepperDash.Essentials.Core; - -/// -/// Extends for devices that can report the current shape of -/// their multiview canvas, along with the position/size, stacking order and routed source of every -/// tile, in a generic, product-agnostic form. This is intentionally decoupled from the routing -/// graph (nodes/edges/tie-lines) built from - -/// it exists to support a separate visualization concern: a mock-up of what is actually displayed -/// on the monitor fed by the decoder, suitable for a React UI or the developer tools Routing page. -/// -public interface IRoutingSinkWithLayoutState : IRoutingSinkWithLayouts -{ - /// - /// Gets the current multiview canvas/tile layout, or null if no layout is currently active or - /// applicable (e.g. the device is not currently in a multiview mode). - /// - MultiviewLayoutState CurrentLayout { get; } - - /// - /// Raised whenever the canvas shape, a tile's geometry/stacking order, or a tile's routed source - /// changes. - /// - event EventHandler LayoutChanged; -} - -/// -/// Event arguments for . -/// -public class MultiviewLayoutStateEventArgs : EventArgs -{ - /// - /// The current multiview layout state at the time this event was raised, or null if no layout - /// is currently active. - /// - public MultiviewLayoutState CurrentLayout { get; } - - /// - /// Initializes a new instance of the class. - /// - public MultiviewLayoutStateEventArgs(MultiviewLayoutState currentLayout) - { - CurrentLayout = currentLayout; - } -} diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayouts.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayouts.cs deleted file mode 100644 index f0711df1..00000000 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithLayouts.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using PepperDash.Core; -using PepperDash.Essentials.Core.Routing; - -namespace PepperDash.Essentials.Core; - - -/// -/// Defines a routing sink (endpoint) device that supports layouts with multiple tile sinks. -/// Implements IRoutingSource so the windowed output of the layout can be routed to other devices. -/// -public interface IRoutingSinkWithLayouts : IRoutingSource -{ - /// - /// Gets the collection of window tile sinks for this routing sink with layouts. - /// Each tile sink represents a single window/tile in the layout and can be routed independently. - /// - Dictionary WindowTileSinks { get; } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs new file mode 100644 index 00000000..5c202914 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSinkWithSwitching.cs @@ -0,0 +1,43 @@ +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Delegate for InputChangedEventHandler + /// + public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort); + + /// + /// Defines the contract for IRoutingSinkWithSwitching + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + /// + /// Executes a switch on the device + /// + /// input selector + void ExecuteSwitch(object inputSelector); + } + + /// + /// Defines the contract for IRoutingSinkWithSwitchingWithInputPort + /// + public interface IRoutingSinkWithSwitchingWithInputPort:IRoutingSinkWithSwitching, IRoutingSinkWithInputPort + { + /// + /// Event raised when the input changes + /// + event InputChangedEventHandler InputChanged; + } + +/* /// + /// Endpoint device like a display, that selects inputs + /// + /// + /// Defines the contract for IRoutingSinkWithSwitching + /// + public interface IRoutingSinkWithSwitching : IRoutingSink + { + void ExecuteSwitch(TSelector inputSelector); + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSlot.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSlot.cs new file mode 100644 index 00000000..2e8c544f --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSlot.cs @@ -0,0 +1,25 @@ +using PepperDash.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PepperDash.Essentials.Core.Routing +{ + /// + /// Defines the contract for IRoutingSlot + /// + public interface IRoutingSlot:IKeyName + { + /// + /// Gets the slot number + /// + int SlotNumber { get; } + + /// + /// Gets the supported signal types + /// + eRoutingSignalType SupportedSignalTypes { get; } + } +} diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs index 880d947f..75bf1bfb 100644 --- a/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs @@ -1,10 +1,9 @@ -using PepperDash.Core; - -namespace PepperDash.Essentials.Core; - -/// -/// Marker interface to identify a device that acts as the origin of a signal path (). -/// -public interface IRoutingSource : IRoutingOutputs, IKeyName +namespace PepperDash.Essentials.Core { + /// + /// Defines the contract for IRoutingSource + /// + public interface IRoutingSource : IRoutingOutputs + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs new file mode 100644 index 00000000..2a80022d --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs @@ -0,0 +1,15 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for IRoutingWithClear + /// + public interface IRoutingWithClear : IRouting + { + /// + /// Clears a route to an output, however a device needs to do that + /// + /// Output to clear + /// signal type to clear + void ClearRoute(object outputSelector, eRoutingSignalType signalType); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs new file mode 100644 index 00000000..e66084a7 --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/IRoutingWithFeedback.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System; + +namespace PepperDash.Essentials.Core +{ + /// + /// Delegate for handling route change events on devices implementing . + /// + /// The routing device where the change occurred. + /// A descriptor of the new route that was established. + /// + /// Delegate for RouteChangedEventHandler + /// + public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute); + /// + /// Defines a routing device () that provides feedback about its current routes. + /// + public interface IRoutingWithFeedback : IRouting + { + /// + /// Gets a list describing the currently active routes on this device. + /// + List CurrentRoutes { get; } + + /// + /// Event triggered when a route changes on this device. + /// + event RouteChangedEventHandler RouteChanged; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs new file mode 100644 index 00000000..0bd818ac --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRouting.cs @@ -0,0 +1,17 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for ITxRouting + /// + public interface ITxRouting : IRoutingNumeric + { + /// + /// Feedback indicating the currently routed video source by its numeric identifier. + /// + IntFeedback VideoSourceNumericFeedback { get; } + /// + /// Feedback indicating the currently routed audio source by its numeric identifier. + /// + IntFeedback AudioSourceNumericFeedback { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs new file mode 100644 index 00000000..abfbff7f --- /dev/null +++ b/src/PepperDash.Essentials.Core/Routing/ITxRoutingWithFeedback.cs @@ -0,0 +1,9 @@ +namespace PepperDash.Essentials.Core +{ + /// + /// Defines the contract for ITxRoutingWithFeedback + /// + public interface ITxRoutingWithFeedback : ITxRouting + { + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/MultiviewLayoutState.cs b/src/PepperDash.Essentials.Core/Routing/MultiviewLayoutState.cs deleted file mode 100644 index 382adebe..00000000 --- a/src/PepperDash.Essentials.Core/Routing/MultiviewLayoutState.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace PepperDash.Essentials.Core; - -/// -/// Describes the current shape of a multiview canvas and the position, size, stacking order and -/// routed source of every visible tile within it. Fully product-agnostic (no dependency on any -/// particular decoder/hardware type) and JSON-serializable so it can be sent as-is over the routing -/// feedback WebSocket () and the routing devices/tie-lines -/// HTTP snapshot (), for -/// rendering a visual mock-up of what is actually displayed on the fed monitor. -/// -public class MultiviewLayoutState -{ - /// - /// Width, in pixels, of the canvas that positions/sizes are expressed - /// against (typically the decoder's current output resolution width). - /// - [JsonProperty("canvasWidth")] - public int CanvasWidth { get; set; } - - /// - /// Height, in pixels, of the canvas that positions/sizes are expressed - /// against (typically the decoder's current output resolution height). - /// - [JsonProperty("canvasHeight")] - public int CanvasHeight { get; set; } - - /// - /// Position, size, stacking order and routed source for every visible tile in the layout. - /// - [JsonProperty("tiles")] - public List Tiles { get; set; } = new List(); -} - -/// -/// Describes a single tile/window within a . -/// -public class MultiviewTileState -{ - /// - /// 1-based tile/window number, matching the key in - /// . - /// - [JsonProperty("tileNumber")] - public int TileNumber { get; set; } - - /// - /// Device key of this tile's child sink, so a client can - /// cross-reference existing routing-feedback data (e.g. sink current-source state) for this tile - /// without re-deriving it. - /// - [JsonProperty("tileSinkKey")] - public string TileSinkKey { get; set; } - - /// Left edge of the tile, in pixels, within the canvas. - [JsonProperty("x")] - public int X { get; set; } - - /// Top edge of the tile, in pixels, within the canvas. - [JsonProperty("y")] - public int Y { get; set; } - - /// Width of the tile, in pixels. - [JsonProperty("width")] - public int Width { get; set; } - - /// Height of the tile, in pixels. - [JsonProperty("height")] - public int Height { get; set; } - - /// - /// Stacking order for overlapping tiles (e.g. picture-in-picture/overlay layouts). Tiles with - /// higher values are drawn on top of tiles with lower values. - /// - [JsonProperty("zOrder")] - public int ZOrder { get; set; } - - /// - /// Device key of the source currently routed to this tile, or null if the tile is empty. - /// - [JsonProperty("sourceDeviceKey")] - public string SourceDeviceKey { get; set; } -} diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs index 32822d0d..e698ae0d 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs @@ -92,13 +92,13 @@ namespace PepperDash.Essentials.Core { Debug.LogVerbose("ExecuteRoutes: {0}", route.ToString()); - if (route.SwitchingDevice is IRoutingSinkWithFeedback sink) + if (route.SwitchingDevice is IRoutingSinkWithSwitching sink) { sink.ExecuteSwitch(route.InputPort.Selector); continue; } - if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice) + if (route.SwitchingDevice is IRouting switchingDevice) { switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType); @@ -117,9 +117,9 @@ namespace PepperDash.Essentials.Core public void ReleaseRoutes(bool clearRoute = false) { - foreach (var route in Routes.Where(r => r.SwitchingDevice is IRoutingMidpointWithFeedback)) + foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting)) { - if (route.SwitchingDevice is IRoutingMidpointWithFeedback switchingDevice) + if (route.SwitchingDevice is IRouting switchingDevice) { if (clearRoute) { diff --git a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs index d17ca8fe..615b24c3 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteDescriptorCollection.cs @@ -1,130 +1,122 @@ -using System; +using System.Collections.Generic; +using System.Linq; using PepperDash.Core; using Serilog.Events; -using System.Collections.Generic; -using System.Linq; -namespace PepperDash.Essentials.Core; - -/// -/// A collection of RouteDescriptors - typically the static DefaultCollection is used -/// -public class RouteDescriptorCollection +namespace PepperDash.Essentials.Core { /// - /// The static default collection of RouteDescriptors. This is typically used for global routing management across the system, but additional collections could be used for specific purposes if desired. + /// A collection of RouteDescriptors - typically the static DefaultCollection is used /// - public static RouteDescriptorCollection DefaultCollection + public class RouteDescriptorCollection { - get + /// + /// Gets the default collection of RouteDescriptors. + /// + public static RouteDescriptorCollection DefaultCollection { - if (_DefaultCollection == null) - _DefaultCollection = new RouteDescriptorCollection(); - return _DefaultCollection; + get + { + if (_DefaultCollection == null) + _DefaultCollection = new RouteDescriptorCollection(); + return _DefaultCollection; + } } - } - private static RouteDescriptorCollection _DefaultCollection; + private static RouteDescriptorCollection _DefaultCollection; - private readonly List RouteDescriptors = new List(); + private readonly List RouteDescriptors = new List(); - /// - /// Event raised when the collection of RouteDescriptors changes (add/remove). This is useful for updating routing status in the UI, for example. - /// - public event EventHandler RouteDescriptorCollectionChanged; + /// + /// Gets an enumerable collection of all RouteDescriptors in this collection. + /// + public IEnumerable Descriptors => RouteDescriptors.AsReadOnly(); - /// - /// Gets an enumerable collection of all RouteDescriptors in this collection. - /// - public IEnumerable Descriptors => RouteDescriptors.AsReadOnly(); - - - /// - /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the - /// destination exists already, it will not be added - in order to preserve - /// proper route releasing. - /// - /// - public void AddRouteDescriptor(RouteDescriptor descriptor) - { - if (descriptor == null) + /// + /// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the + /// destination exists already, it will not be added - in order to preserve + /// proper route releasing. + /// + /// + public void AddRouteDescriptor(RouteDescriptor descriptor) { - return; - } + if (descriptor == null) + { + return; + } - // Check if a route already exists with the same source, destination, input port, AND signal type - var existingRoute = RouteDescriptors.FirstOrDefault(t => - t.Source == descriptor.Source && - t.Destination == descriptor.Destination && - t.SignalType == descriptor.SignalType && - ((t.InputPort == null && descriptor.InputPort == null) || - (t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))); + // Check if a route already exists with the same source, destination, input port, AND signal type + var existingRoute = RouteDescriptors.FirstOrDefault(t => + t.Source == descriptor.Source && + t.Destination == descriptor.Destination && + t.SignalType == descriptor.SignalType && + ((t.InputPort == null && descriptor.InputPort == null) || + (t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key)) && + ((t.OutputPort == null && descriptor.OutputPort == null) || + (t.OutputPort != null && descriptor.OutputPort != null && t.OutputPort.Key == descriptor.OutputPort.Key))); - if (existingRoute != null) - { - Debug.LogMessage(LogEventLevel.Information, descriptor.Destination, - "Route from {0} to {1}:{2} ({3}) already exists in this collection", + if (existingRoute != null) + { + Debug.LogInformation(descriptor.Destination, + "Route from {source}:{outputPort} to {destination}:{inputPort} ({signalType}) already exists in this collection", + descriptor?.Source?.Key, + descriptor?.OutputPort?.Key ?? "auto", + descriptor?.Destination?.Key, + descriptor?.InputPort?.Key ?? "auto", + descriptor?.SignalType + ); + return; + } + Debug.LogVerbose("Adding route descriptor: {source}:{outputPort} -> {destination}:{inputPort} ({signalType})", descriptor?.Source?.Key, + descriptor?.OutputPort?.Key ?? "auto", descriptor?.Destination?.Key, descriptor?.InputPort?.Key ?? "auto", descriptor?.SignalType); - return; + RouteDescriptors.Add(descriptor); } - Debug.LogMessage(LogEventLevel.Verbose, "Adding route descriptor: {0} -> {1}:{2} ({3})", - descriptor?.Source?.Key, - descriptor?.Destination?.Key, - descriptor?.InputPort?.Key ?? "auto", - descriptor?.SignalType); - RouteDescriptors.Add(descriptor); - RouteDescriptorCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Gets the RouteDescriptor for a destination - /// - /// null if no RouteDescriptor for a destination exists - public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) - { - Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}'", destination?.Key ?? null); - - return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); - } - - /// - /// Gets the route descriptor for a specific destination and input port - /// - /// The destination device - /// The input port key - /// The matching RouteDescriptor or null if not found - public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey) - { - Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination && rd.InputPort != null && rd.InputPort.Key == inputPortKey); - } - - /// - /// Returns the RouteDescriptor for a given destination AND removes it from collection. - /// Returns null if no route with the provided destination exists. - /// - /// The destination device - /// The input port key (optional) - /// The matching RouteDescriptor or null if not found - public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string inputPortKey = "") - { - Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - - var descr = string.IsNullOrEmpty(inputPortKey) - ? GetRouteDescriptorForDestination(destination) - : GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey); - if (descr != null) + /// + /// Gets the RouteDescriptor for a destination. Returns null if no RouteDescriptor for a destination exists. + /// + public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination) { - RouteDescriptors.Remove(descr); - RouteDescriptorCollectionChanged?.Invoke(this, EventArgs.Empty); + Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}'", destination?.Key ?? null); + + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination); } - Debug.LogMessage(LogEventLevel.Information, "Found route descriptor {routeDescriptor}", destination, descr); + /// + /// Gets the route descriptor for a specific destination and input port + /// + /// The destination device + /// The input port key + /// The matching RouteDescriptor or null if not found + public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey) + { + Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination && rd.InputPort != null && rd.InputPort.Key == inputPortKey); + } - return descr; + /// + /// Removes a RouteDescriptor from the collection based on the specified destination and input port key. + /// + /// The destination for which the route descriptor is to be removed. + /// The key of the input port associated with the route descriptor. If empty, the method will attempt to remove a descriptor based solely on the destination. + /// The removed RouteDescriptor object if a matching descriptor was found; otherwise, null. + public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string inputPortKey = "") + { + Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + + var descr = string.IsNullOrEmpty(inputPortKey) + ? GetRouteDescriptorForDestination(destination) + : GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey); + if (descr != null) + RouteDescriptors.Remove(descr); + + Debug.LogMessage(LogEventLevel.Information, "Found route descriptor {routeDescriptor}", destination, descr); + + return descr; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs index 08752257..30c0e5f8 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequest.cs @@ -2,76 +2,77 @@ using Serilog.Events; using System; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a request to establish a route between a source and a destination device. -/// -public class RouteRequest +namespace PepperDash.Essentials.Core { /// - /// The specific input port on the destination device to use for the route. Can be null if the port should be automatically determined or is not applicable. + /// Represents a RouteRequest /// - public RoutingInputPort DestinationPort { get; set; } - - /// - /// The specific output port on the source device to use for the route. Can be null if the port should be automatically determined or is not applicable. - /// - public RoutingOutputPort SourcePort { get; set; } - - /// - /// The destination device (sink or midpoint) for the route. - /// - public IRoutingInputs Destination { get; set; } - - /// - /// The source device for the route. - /// - public IRoutingOutputs Source { get; set; } - - /// - /// The type of signal being routed (e.g., Audio, Video, AudioVideo). - /// - public eRoutingSignalType SignalType { get; set; } - - /// - /// Handles the route request after a device's cooldown period has finished. - /// This method is typically subscribed to the IsCoolingDownFeedback.OutputChange event. - /// - /// The object that triggered the event (usually the cooling device). - /// Event arguments indicating the cooldown state change. - public void HandleCooldown(object sender, FeedbackEventArgs args) + public class RouteRequest { - try + /// + /// The specific input port on the destination device to use for the route. Can be null if the port should be automatically determined or is not applicable. + /// + public RoutingInputPort DestinationPort { get; set; } + + /// + /// Gets or sets the SourcePort + /// + public RoutingOutputPort SourcePort { get; set; } + + /// + /// Gets or sets the Destination + /// + public IRoutingInputs Destination { get; set; } + + /// + /// Gets or sets the Source + /// + public IRoutingOutputs Source { get; set; } + + /// + /// Gets or sets the SignalType + /// + public eRoutingSignalType SignalType { get; set; } + + /// + /// Handles the route request after a device's cooldown period has finished. + /// This method is typically subscribed to the IsCoolingDownFeedback.OutputChange event. + /// + /// The object that triggered the event (usually the cooling device). + /// Event arguments indicating the cooldown state change. + public void HandleCooldown(object sender, FeedbackEventArgs args) { - Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination?.Key ?? "empty destination", DestinationPort?.Key ?? "no destination port", Source?.Key ?? "empty source", SourcePort?.Key ?? "empty source port", SignalType.ToString()); - - if (args.BoolValue == true) + try { - return; - } + Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination?.Key ?? "empty destination", DestinationPort?.Key ?? "no destination port", Source?.Key ?? "empty source", SourcePort?.Key ?? "empty source port", SignalType.ToString()); - Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination?.Key, Source?.Key); + if (args.BoolValue == true) + { + return; + } - Destination.ReleaseAndMakeRoute(Source, SignalType, DestinationPort?.Key ?? string.Empty, SourcePort?.Key ?? string.Empty); + Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination?.Key, Source?.Key); - if (sender is IWarmingCooling coolingDevice) + Destination.ReleaseAndMakeRoute(Source, SignalType, DestinationPort?.Key ?? string.Empty, SourcePort?.Key ?? string.Empty); + + if (sender is IWarmingCooling coolingDevice) + { + Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key); + coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + } + } catch(Exception ex) { - Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key); - coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown; + Debug.LogMessage(ex, "Exception handling cooldown", Destination); } - } catch(Exception ex) + } + + /// + /// Returns a string representation of the route request. + /// + /// A string describing the source and destination of the route request. + public override string ToString() { - Debug.LogMessage(ex, "Exception handling cooldown", Destination); + return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}"; } } - - /// - /// Returns a string representation of the route request. - /// - /// A string describing the source and destination of the route request. - public override string ToString() - { - return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}"; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs b/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs index 1f7ab486..812f1399 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteRequestQueueItem.cs @@ -3,86 +3,87 @@ using PepperDash.Essentials.Core.Queues; using System; using Serilog.Events; -namespace PepperDash.Essentials.Core.Routing; - -/// -/// Represents an item in the route request queue. -/// -public class RouteRequestQueueItem : IQueueMessage +namespace PepperDash.Essentials.Core.Routing { /// - /// The action to perform for the route request. + /// Represents a RouteRequestQueueItem /// - private readonly Action action; - /// - /// The route request data. - /// - private readonly RouteRequest routeRequest; - - /// - /// Initializes a new instance of the class. - /// - /// The action to perform. - /// The route request data. - public RouteRequestQueueItem(Action routeAction, RouteRequest request) + public class RouteRequestQueueItem : IQueueMessage { - action = routeAction; - routeRequest = request; + /// + /// The action to perform for the route request. + /// + private readonly Action action; + /// + /// The route request data. + /// + private readonly RouteRequest routeRequest; + + /// + /// Initializes a new instance of the class. + /// + /// The action to perform. + /// The route request data. + public RouteRequestQueueItem(Action routeAction, RouteRequest request) + { + action = routeAction; + routeRequest = request; + } + + /// + /// Dispatches the route request action. + /// + public void Dispatch() + { + Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest); + action(routeRequest); + } } /// - /// Dispatches the route request action. + /// Represents a ReleaseRouteQueueItem /// - public void Dispatch() + public class ReleaseRouteQueueItem : IQueueMessage { - Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest); - action(routeRequest); - } -} - -/// -/// Represents an item in the queue for releasing a route. -/// -public class ReleaseRouteQueueItem : IQueueMessage -{ - /// - /// The action to perform for releasing the route. - /// - private readonly Action action; - /// - /// The destination device whose route is being released. - /// - private readonly IRoutingInputs destination; - /// - /// The specific input port key on the destination to release, or null/empty for any/default. - /// - private readonly string inputPortKey; - /// - /// Indicates whether to clear the route (send null) or just release the usage tracking. - /// - private readonly bool clearRoute; - - /// - /// Initializes a new instance of the class. - /// - /// The action to perform. - /// The destination device. - /// The input port key. - /// True to clear the route, false to just release. - public ReleaseRouteQueueItem(Action action, IRoutingInputs destination, string inputPortKey, bool clearRoute) - { - this.action = action; - this.destination = destination; - this.inputPortKey = inputPortKey; - this.clearRoute = clearRoute; - } - - /// - /// Dispatches the release route action. - /// - public void Dispatch() - { - Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); - action(destination, inputPortKey, clearRoute); + /// + /// The action to perform for releasing the route. + /// + private readonly Action action; + /// + /// The destination device whose route is being released. + /// + private readonly IRoutingInputs destination; + /// + /// The specific input port key on the destination to release, or null/empty for any/default. + /// + private readonly string inputPortKey; + /// + /// Indicates whether to clear the route (send null) or just release the usage tracking. + /// + private readonly bool clearRoute; + + /// + /// Initializes a new instance of the class. + /// + /// The action to perform. + /// The destination device. + /// The input port key. + /// True to clear the route, false to just release. + public ReleaseRouteQueueItem(Action action, IRoutingInputs destination, string inputPortKey, bool clearRoute) + { + this.action = action; + this.destination = destination; + this.inputPortKey = inputPortKey; + this.clearRoute = clearRoute; + } + + /// + /// Dispatch method + /// + public void Dispatch() + { + Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey); + action(destination, inputPortKey, clearRoute); + } } } diff --git a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs index 2e526589..227e6d5b 100644 --- a/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs +++ b/src/PepperDash.Essentials.Core/Routing/RouteSwitchDescriptor.cs @@ -2,7 +2,6 @@ { /// /// Represents a RouteSwitchDescriptor - /// This represents a switch to be made on an IRoutingInputs device, which could be a matrix switcher or a sink device. /// public class RouteSwitchDescriptor { @@ -46,7 +45,7 @@ /// public override string ToString() { - if (SwitchingDevice is IRoutingMidpointWithFeedback) + if (SwitchingDevice is IRouting) return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}"; else return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}"; diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs index 0ef974b9..7fe41c5c 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Timers; @@ -15,21 +14,19 @@ namespace PepperDash.Essentials.Core.Routing public class RoutingFeedbackManager : EssentialsDevice { /// - /// Maps midpoint device keys to the set of downstream sink input ports, derived from the - /// static tie-line topology. Because it is built from topology rather than a sink's currently - /// reported input, sinks that never report an input (i.e. - /// stays null, such as codecs fed from an external matrix) are still mapped and updated. + /// Maps midpoint device keys to the set of sink device keys that are downstream /// - private Dictionary> midpointToSinkInputsMap; + private Dictionary> midpointToSinksMap; /// - /// Debounce timers for each sink device to prevent rapid successive updates. Must be a - /// ConcurrentDictionary - it's mutated both from whatever thread calls UpdateDestination and - /// from each Timer's Elapsed callback (which fires on a threadpool timer thread), so a plain - /// Dictionary here would have its internal state corrupted by the concurrent Insert/Remove - /// calls, surfacing as an IndexOutOfRangeException from deep inside Dictionary internals. + /// Debounce timers for each sink device to prevent rapid successive updates /// - private readonly ConcurrentDictionary updateTimers = new ConcurrentDictionary(); + private readonly Dictionary updateTimers = new Dictionary(); + + /// + /// Lock object protecting all access to . + /// + private readonly object _timerLock = new object(); /// /// Debounce delay in milliseconds @@ -50,39 +47,32 @@ namespace PepperDash.Essentials.Core.Routing } /// - /// Builds a map of which sink input ports are downstream of each midpoint device - /// for performance optimization in HandleMidpointUpdate. - /// The map is derived from the static tie-line topology (every sink input port is traced - /// upstream), so it does not depend on a sink having already reported its current input. + /// Builds a map of which sink devices are downstream of each midpoint device + /// for performance optimization in HandleMidpointUpdate /// private void BuildMidpointSinkMap() { - midpointToSinkInputsMap = new Dictionary>(); + midpointToSinksMap = new Dictionary>(); - var sinks = DeviceManager.AllDevices.OfType(); + var sinks = DeviceManager.AllDevices.OfType(); foreach (var sink in sinks) { - // Trace from every input port on the sink (static topology) rather than only the - // currently-selected input. Sinks that never report an input still get mapped. - var inputPorts = (sink as IRoutingInputs)?.InputPorts; - if (inputPorts == null) + if (sink.CurrentInputPort == null) continue; - foreach (var inputPort in inputPorts) + // Find all upstream midpoints for this sink + var upstreamMidpoints = GetUpstreamMidpoints(sink); + + foreach (var midpointKey in upstreamMidpoints) { - var upstreamMidpoints = GetUpstreamMidpointsForInput(inputPort); + if (string.IsNullOrEmpty(midpointKey)) + continue; - foreach (var midpointKey in upstreamMidpoints) - { - if (!midpointToSinkInputsMap.TryGetValue(midpointKey, out var inputs)) - { - inputs = new HashSet(); - midpointToSinkInputsMap[midpointKey] = inputs; - } + if (!midpointToSinksMap.ContainsKey(midpointKey)) + midpointToSinksMap[midpointKey] = new HashSet(); - inputs.Add(inputPort); - } + midpointToSinksMap[midpointKey].Add(sink.Key); } } @@ -90,25 +80,24 @@ namespace PepperDash.Essentials.Core.Routing Serilog.Events.LogEventLevel.Information, "Built midpoint-to-sink map with {count} midpoints", this, - midpointToSinkInputsMap.Count + midpointToSinksMap.Count ); } /// - /// Gets all upstream midpoint device keys reachable from a specific sink input port - /// by walking the static tie-line topology. + /// Gets all upstream midpoint device keys for a given sink /// - private HashSet GetUpstreamMidpointsForInput(RoutingInputPort inputPort) + private HashSet GetUpstreamMidpoints(IRoutingSinkWithSwitchingWithInputPort sink) { var result = new HashSet(); var visited = new HashSet(); - if (inputPort == null) + if (sink.CurrentInputPort == null) return result; var tieLine = TieLineCollection.Default.FirstOrDefault(tl => - tl.DestinationPort.Key == inputPort.Key && - tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key); + tl.DestinationPort.Key == sink.CurrentInputPort.Key && + tl.DestinationPort.ParentDevice.Key == sink.CurrentInputPort.ParentDevice.Key); if (tieLine == null) return result; @@ -127,9 +116,10 @@ namespace PepperDash.Essentials.Core.Routing visited.Add(tieLine.SourcePort.ParentDevice.Key); - if (tieLine.SourcePort.ParentDevice is IRoutingMidpointWithFeedback midpoint) + if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint) { - midpoints.Add(midpoint.Key); + if (!string.IsNullOrEmpty(midpoint.Key)) + midpoints.Add(midpoint.Key); // Find upstream TieLines connected to this midpoint's inputs var midpointInputs = (midpoint as IRoutingInputs)?.InputPorts; @@ -149,11 +139,11 @@ namespace PepperDash.Essentials.Core.Routing } /// - /// Subscribes to the RouteChanged event on all devices implementing . + /// Subscribes to the RouteChanged event on all devices implementing . /// private void SubscribeForMidpointFeedback() { - var midpointDevices = DeviceManager.AllDevices.OfType(); + var midpointDevices = DeviceManager.AllDevices.OfType(); foreach (var device in midpointDevices) { @@ -162,12 +152,12 @@ namespace PepperDash.Essentials.Core.Routing } /// - /// Subscribes to the InputChanged event on all devices implementing . + /// Subscribes to the InputChanged event on all devices implementing . /// private void SubscribeForSinkFeedback() { var sinkDevices = - DeviceManager.AllDevices.OfType(); + DeviceManager.AllDevices.OfType(); foreach (var device in sinkDevices) { @@ -182,41 +172,29 @@ namespace PepperDash.Essentials.Core.Routing /// The midpoint device that reported a route change. /// The descriptor of the new route. private void HandleMidpointUpdate( - IRoutingMidpointWithFeedback midpoint, + IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute ) { try { // Only update affected sinks (performance optimization) - if (midpointToSinkInputsMap != null && midpointToSinkInputsMap.TryGetValue(midpoint.Key, out var affectedInputPorts)) + if (midpointToSinksMap != null && midpointToSinksMap.TryGetValue(midpoint.Key, out var affectedSinkKeys)) { Debug.LogMessage( Serilog.Events.LogEventLevel.Debug, - "Midpoint {midpoint} changed, updating {count} downstream sink inputs", + "Midpoint {midpoint} changed, updating {count} downstream sinks", this, midpoint.Key, - affectedInputPorts.Count + affectedSinkKeys.Count ); - // Avoid redundant updates when a feedback-reporting sink has several mapped inputs. - var updatedSinkKeys = new HashSet(); - - foreach (var inputPort in affectedInputPorts) + foreach (var sinkKey in affectedSinkKeys) { - if (!(inputPort.ParentDevice is IRoutingSinkWithFeedback sink)) - continue; - - // Sinks that report their input drive updates off the currently-selected input. - // Sinks that never report an input (CurrentInputPort == null, e.g. a codec fed - // from an external matrix) fall back to the static topology input port so that - // matrix route changes still propagate. - var portToUse = sink.CurrentInputPort ?? inputPort; - - if (sink.CurrentInputPort != null && !updatedSinkKeys.Add(sink.Key)) - continue; - - UpdateDestination(sink, portToUse); + if (DeviceManager.GetDeviceForKey(sinkKey) is IRoutingSinkWithSwitchingWithInputPort sink) + { + UpdateDestination(sink, sink.CurrentInputPort); + } } } else @@ -241,21 +219,64 @@ namespace PepperDash.Essentials.Core.Routing } } + /// + /// Removes a sink from every midpoint set in the map and re-adds it based on its + /// current input port. Call this whenever a sink's selected input changes so that + /// HandleMidpointUpdate always sees an up-to-date downstream set. + /// + private void RebuildMapForSink(IRoutingSinkWithSwitchingWithInputPort sink) + { + if (midpointToSinksMap == null) + return; + + // Remove this sink from all existing midpoint sets + foreach (var set in midpointToSinksMap.Values) + set.Remove(sink.Key); + + // Drop any midpoint entries that are now empty + var emptyKeys = midpointToSinksMap + .Where(kvp => kvp.Value.Count == 0) + .Select(kvp => kvp.Key) + .ToList(); + foreach (var k in emptyKeys) + midpointToSinksMap.Remove(k); + + // Re-add the sink under every midpoint that is upstream of its new input + if (sink.CurrentInputPort == null) + return; + + var upstreamMidpoints = GetUpstreamMidpoints(sink); + foreach (var midpointKey in upstreamMidpoints) + { + if (string.IsNullOrEmpty(midpointKey)) + continue; + + if (!midpointToSinksMap.ContainsKey(midpointKey)) + midpointToSinksMap[midpointKey] = new HashSet(); + + midpointToSinksMap[midpointKey].Add(sink.Key); + } + } + /// /// Handles the InputChanged event from a sink device. - /// Triggers an update for the specific sink device. + /// Updates the midpoint-to-sink map for the new input path, then triggers + /// a source-info update for the sink. /// /// The sink device that reported an input change. /// The new input port selected on the sink device. private void HandleSinkUpdate( - IRoutingSinkWithFeedback sender, + IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort ) { try { - UpdateDestination(sender, currentInputPort); + // Keep the map current so HandleMidpointUpdate can find this sink + if (sender is IRoutingSinkWithSwitchingWithInputPort sinkWithInputPort) + RebuildMapForSink(sinkWithInputPort); + UpdateDestination(sender, currentInputPort); } catch (Exception ex) { @@ -277,31 +298,22 @@ namespace PepperDash.Essentials.Core.Routing /// The destination sink device to update. /// The currently selected input port on the destination device. private void UpdateDestination( - IRoutingSinkWithFeedback destination, + IRoutingSinkWithSwitching destination, RoutingInputPort inputPort ) { if (destination == null) return; - // Keyed by destination AND input port, not just destination: a single sink can have - // several independently-routed input ports (e.g. a codec with multiple simultaneous - // USB camera inputs, all downstream of the same midpoint). Keying by destination alone - // meant updates for one port would cancel/replace the still-pending debounce timer for - // a sibling port on the same sink, so only the last-processed port's update ever - // actually ran - the others silently never got refreshed. - var key = destination.Key + ":" + (inputPort?.Key ?? string.Empty); + var key = destination.Key; - // Cancel existing timer for this specific sink/port combination - if (updateTimers.TryGetValue(key, out var existingTimer)) - { - existingTimer.Stop(); - existingTimer.Dispose(); - } + // Cancel and replace any existing timer under the lock so no callback + // can race with us while we swap the entry. + Timer timerToDispose = null; + Timer newTimer = null; - // Start new debounced timer - var timer = new Timer(DEBOUNCE_MS) { AutoReset = false }; - timer.Elapsed += (sender, e) => + newTimer = new Timer(DEBOUNCE_MS) { AutoReset = false }; + newTimer.Elapsed += (s, e) => { try { @@ -319,14 +331,36 @@ namespace PepperDash.Essentials.Core.Routing } finally { - if (updateTimers.TryRemove(key, out var timerToDispose)) + // Remove the entry first so a concurrent UpdateDestination call + // cannot re-dispose whatever timer we're about to dispose. + Timer selfTimer = null; + lock (_timerLock) { - timerToDispose?.Dispose(); + if (updateTimers.TryGetValue(key, out var current) && ReferenceEquals(current, newTimer)) + { + selfTimer = current; + updateTimers.Remove(key); + } } + selfTimer?.Dispose(); } }; - timer.Start(); - updateTimers[key] = timer; + + lock (_timerLock) + { + if (updateTimers.TryGetValue(key, out var existingTimer)) + timerToDispose = existingTimer; + + updateTimers[key] = newTimer; + } + + // Dispose the old timer outside the lock to avoid holding the lock during disposal. + // Dispose implicitly stops the timer, preventing its Elapsed event from firing. + timerToDispose?.Dispose(); + + // Start after the lock is released so the Elapsed callback cannot deadlock + // trying to acquire _timerLock while we still hold it. + newTimer.Start(); } /// @@ -334,7 +368,7 @@ namespace PepperDash.Essentials.Core.Routing /// Called after debounce delay. /// private void UpdateDestinationImmediate( - IRoutingSinkWithFeedback destination, + IRoutingSinkWithSwitching destination, RoutingInputPort inputPort ) { @@ -376,7 +410,15 @@ namespace PepperDash.Essentials.Core.Routing inputPort ); + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = inputPort.Key, + }; + destination.CurrentSourceInfo = tempSourceListItem; + ; + destination.CurrentSourceInfoKey = "$transient"; return; } } @@ -402,45 +444,121 @@ namespace PepperDash.Essentials.Core.Routing inputPort ); + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = "None", + }; - // determine all the tie lines between the source and destination to determine the signal type - // the type is the union of all the tie lines between the source and destination - - // For now we assume the type matches the tie line connected to the destination - destination.SetCurrentSource(firstTieLine.Type, null); - - // remove existing descriptor if any - RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key); - + destination.CurrentSourceInfo = tempSourceListItem; + destination.CurrentSourceInfoKey = string.Empty; return; } } catch (Exception ex) { - Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex); + Debug.LogError(this, "Error getting sourceTieLine: {message}", ex.Message); + Debug.LogDebug(ex, "StackTrace: "); return; } + // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine); - // Get the routes from the destination to the source using the existing GetRouteToSource method - var routes = destination.GetRouteToSource( - sourceTieLine.SourcePort.ParentDevice as IRoutingOutputs, - sourceTieLine.Type, - inputPort, - sourceTieLine.SourcePort - ); + // Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet. + var room = DeviceManager + .AllDevices.OfType() + .FirstOrDefault( + (r) => + { + if (r is IHasMultipleDisplays roomMultipleDisplays) + { + return roomMultipleDisplays.Displays.Any(d => + d.Value.Key == destination.Key + ); + } - // remove existing descriptor if any - RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPort.Key); + if (r is IHasDefaultDisplay roomDefaultDisplay) + { + return roomDefaultDisplay.DefaultDisplay.Key == destination.Key; + } - // Add the new route descriptors to the collection - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item1); + if (ConfigReader.ConfigObject.GetDestinationListForKey(r.DestinationListKey)?.FirstOrDefault(d => d.Value.SinkKey == destination.Key) != null) + { + return true; + } - if(routes.Item2 != null) + return false; + } + ); + + if (room == null) { - RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(routes.Item2); + Debug.LogMessage( + Serilog.Events.LogEventLevel.Debug, + "No room found for display {destination}", + this, + destination.Key + ); + return; } + // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key); + + var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey); + + if (sourceList == null) + { + Debug.LogDebug(this, + "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", + room.SourceListKey, + sourceTieLine + ); + return; + } + + // Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key); + + var sourceListItem = sourceList.FirstOrDefault(sli => + { + //// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, + // "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}", + // this, + // sli.Key, + // sli.Value.SourceKey, + // sourceTieLine.SourcePort.ParentDevice.Key); + + return sli.Value.SourceKey.Equals( + sourceTieLine.SourcePort.ParentDevice.Key, + StringComparison.InvariantCultureIgnoreCase + ); + }); + + var source = sourceListItem.Value; + var sourceKey = sourceListItem.Key; + + if (source == null) + { + Debug.LogDebug(this, + "No source found for device {key}. Creating transient source for {destination}", + sourceTieLine.SourcePort.ParentDevice.Key, + destination + ); + + var tempSourceListItem = new SourceListItem + { + SourceKey = "$transient", + Name = sourceTieLine.SourcePort.Key, + }; + + destination.CurrentSourceInfoKey = "$transient"; + destination.CurrentSourceInfo = tempSourceListItem; + return; + } + + //Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Got Source {@source} with key {sourceKey}", this, source, sourceKey); + + destination.CurrentSourceInfoKey = sourceKey; + destination.CurrentSourceInfo = source; } /// @@ -455,10 +573,8 @@ namespace PepperDash.Essentials.Core.Routing { if (!(tieLine.DestinationPort.ParentDevice is IRoutingInputs sink)) { - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, + Debug.LogDebug(this, "TieLine destination {device} is not IRoutingInputs", - this, tieLine.DestinationPort.ParentDevice.Key ); return null; @@ -467,7 +583,7 @@ namespace PepperDash.Essentials.Core.Routing // Get all potential sources (devices that only have outputs, not inputs+outputs) var sources = DeviceManager.AllDevices .OfType() - .Where(s => !(s is IRoutingMidpoint)); + .Where(s => !(s is IRoutingInputsOutputs)); // Try each signal type that this TieLine supports var signalTypes = new[] @@ -475,6 +591,9 @@ namespace PepperDash.Essentials.Core.Routing eRoutingSignalType.Audio, eRoutingSignalType.Video, eRoutingSignalType.AudioVideo, + eRoutingSignalType.SecondaryAudio, + eRoutingSignalType.UsbInput, + eRoutingSignalType.UsbOutput }; foreach (var signalType in signalTypes) @@ -494,17 +613,21 @@ namespace PepperDash.Essentials.Core.Routing if (route != null && route.Routes != null && route.Routes.Count > 0) { - // Found a valid route - return the source TieLine + // Routes[0] is the hop nearest the source: its InputPort is the + // port on the first switching device that receives the signal from + // the source side. The TieLine whose DestinationPort matches that + // port is the exact tie that was traversed, giving us the precise + // source output port via SourcePort — regardless of how many output + // ports the source device has. + var firstHop = route.Routes[0]; var sourceTieLine = TieLineCollection.Default.FirstOrDefault(tl => - tl.SourcePort.ParentDevice.Key == source.Key && - tl.Type.HasFlag(signalType)); + tl.DestinationPort.Key == firstHop.InputPort.Key && + tl.DestinationPort.ParentDevice.Key == firstHop.InputPort.ParentDevice.Key); if (sourceTieLine != null) { - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, - "Found route from {source} to {sink} with {count} hops", - this, + Debug.LogDebug(this, + "Found route from {source} to {sink} with {count} hops", source.Key, sink.Key, route.Routes.Count @@ -515,24 +638,16 @@ namespace PepperDash.Essentials.Core.Routing } } - Debug.LogMessage( - Serilog.Events.LogEventLevel.Debug, - "No route found to any source from {sink}", - this, - sink.Key - ); + Debug.LogDebug(this, "No route found to any source from {sink}", sink.Key); return null; } catch (Exception ex) { - Debug.LogMessage( - ex, - "Error getting root tieLine: {Exception}", - this, - ex - ); + Debug.LogError(this, "Error getting root tieLine: {message}", ex.Message); + Debug.LogDebug(ex, "StackTrace: "); + return null; } } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs index fbc575e9..4e50f689 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs @@ -2,18 +2,18 @@ using System; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a basic routing input port on a device. -/// -public class RoutingInputPort : RoutingPort - { +namespace PepperDash.Essentials.Core +{ /// - /// The IRoutingInputs object this lives on + /// Represents a RoutingInputPort /// - [JsonIgnore] - public IRoutingInputs ParentDevice { get; private set; } + public class RoutingInputPort : RoutingPort + { + /// + /// The IRoutingInputs object this lives on + /// + [JsonIgnore] + public IRoutingInputs ParentDevice { get; private set; } /// /// Constructor for a basic RoutingInputPort @@ -50,13 +50,49 @@ public class RoutingInputPort : RoutingPort ParentDevice = parent; } - /// - /// Returns a string representation of the input port. - /// - /// A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType". - public override string ToString() - { - return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; + /// + /// Returns a string representation of the input port. + /// + /// A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType". + /// + public override string ToString() + { + return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; + } } -} + /*/// + /// Basic RoutingInput with no statuses. + /// + public class RoutingInputPort : RoutingPort + { + /// + /// Gets or sets the ParentDevice + /// + public IRoutingInputs ParentDevice { get; private set; } + + /// + /// Constructor for a basic RoutingInputPort + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingInputs object this lives on + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + /// + /// Constructor for a virtual routing input port that lives inside a device. For example + /// the ports that link a DM card to a DM matrix bus + /// + /// true for internal ports + public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingInputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs index d98c1ae3..2ecf75c4 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingInputPortWithVideoStatuses.cs @@ -1,30 +1,30 @@ -namespace PepperDash.Essentials.Core; - -/// -/// Represents a routing input port that provides video status feedback (e.g., sync, resolution). -/// Suitable for devices like DM transmitters or DM input cards. -/// -public class RoutingInputPortWithVideoStatuses : RoutingInputPort +namespace PepperDash.Essentials.Core { - /// - /// Provides feedback outputs for video statuses associated with this port. - /// - public VideoStatusOutputs VideoStatus { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The unique key for this port. - /// The signal type supported by this port. - /// The physical connection type of this port. - /// An object used to refer to this port in the parent device's ExecuteSwitch method. - /// The device this port belongs to. - /// A containing delegates to retrieve video status values. - public RoutingInputPortWithVideoStatuses(string key, - eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, - IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : - base(key, type, connType, selector, parent) + /// + /// Represents a RoutingInputPortWithVideoStatuses + /// + public class RoutingInputPortWithVideoStatuses : RoutingInputPort { - VideoStatus = new VideoStatusOutputs(funcs); + /// + /// Provides feedback outputs for video statuses associated with this port. + /// + public VideoStatusOutputs VideoStatus { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The unique key for this port. + /// The signal type supported by this port. + /// The physical connection type of this port. + /// An object used to refer to this port in the parent device's ExecuteSwitch method. + /// The device this port belongs to. + /// A containing delegates to retrieve video status values. + public RoutingInputPortWithVideoStatuses(string key, + eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, + IRoutingInputs parent, VideoStatusFuncsWrapper funcs) : + base(key, type, connType, selector, parent) + { + VideoStatus = new VideoStatusOutputs(funcs); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs index 89cd3d5c..a2025dad 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingNumericEventArgs.cs @@ -1,82 +1,83 @@ using System; -namespace PepperDash.Essentials.Core; - -/// -/// Provides event arguments for routing changes, potentially including numeric or port object references. -/// -public class RoutingNumericEventArgs : EventArgs +namespace PepperDash.Essentials.Core { /// - /// The numeric representation of the output, if applicable. + /// Represents a RoutingNumericEventArgs /// - public uint? Output { get; set; } - /// - /// The numeric representation of the input, if applicable. - /// - public uint? Input { get; set; } - - /// - /// The type of signal involved in the routing change. - /// - public eRoutingSignalType SigType { get; set; } - /// - /// The input port involved in the routing change, if applicable. - /// - public RoutingInputPort InputPort { get; set; } - /// - /// The output port involved in the routing change, if applicable. - /// - public RoutingOutputPort OutputPort { get; set; } - - /// - /// Initializes a new instance of the class using numeric identifiers. - /// - /// The numeric output identifier. - /// The numeric input identifier. - /// The signal type. - public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) + public class RoutingNumericEventArgs : EventArgs { - } + /// + /// The numeric representation of the output, if applicable. + /// + public uint? Output { get; set; } + /// + /// The numeric representation of the input, if applicable. + /// + public uint? Input { get; set; } - /// - /// Initializes a new instance of the class using port objects. - /// - /// The output port object. - /// The input port object. - /// The signal type. - public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, - eRoutingSignalType sigType) - : this(null, null, outputPort, inputPort, sigType) - { - } + /// + /// The type of signal involved in the routing change. + /// + public eRoutingSignalType SigType { get; set; } + /// + /// The input port involved in the routing change, if applicable. + /// + public RoutingInputPort InputPort { get; set; } + /// + /// The output port involved in the routing change, if applicable. + /// + public RoutingOutputPort OutputPort { get; set; } - /// - /// Initializes a new instance of the class with default values. - /// - public RoutingNumericEventArgs() - : this(null, null, null, null, 0) - { + /// + /// Initializes a new instance of the class using numeric identifiers. + /// + /// The numeric output identifier. + /// The numeric input identifier. + /// The signal type. + public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType) + { + } - } + /// + /// Initializes a new instance of the class using port objects. + /// + /// The output port object. + /// The input port object. + /// The signal type. + public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort, + eRoutingSignalType sigType) + : this(null, null, outputPort, inputPort, sigType) + { + } - /// - /// Initializes a new instance of the class with potentially mixed identifiers. - /// - /// The numeric output identifier (optional). - /// The numeric input identifier (optional). - /// The output port object (optional). - /// The input port object (optional). - /// The signal type. - public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, - RoutingInputPort inputPort, eRoutingSignalType sigType) - { - OutputPort = outputPort; - InputPort = inputPort; + /// + /// Initializes a new instance of the class with default values. + /// + public RoutingNumericEventArgs() + : this(null, null, null, null, 0) + { + + } - Output = output; - Input = input; - SigType = sigType; + /// + /// Initializes a new instance of the class with potentially mixed identifiers. + /// + /// The numeric output identifier (optional). + /// The numeric input identifier (optional). + /// The output port object (optional). + /// The input port object (optional). + /// The signal type. + public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort, + RoutingInputPort inputPort, eRoutingSignalType sigType) + { + OutputPort = outputPort; + InputPort = inputPort; + + Output = output; + Input = input; + SigType = sigType; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs index 747106ce..c1cadfb1 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs @@ -2,63 +2,105 @@ using System; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a basic routing output port on a device. -/// -public class RoutingOutputPort : RoutingPort +namespace PepperDash.Essentials.Core { /// /// Represents a RoutingOutputPort /// - [JsonIgnore] - public IRoutingOutputs ParentDevice { get; private set; } - - /// - /// Tracks which destinations are currently using this output port. - /// - public InUseTracking InUseTracker { get; private set; } - - - /// - /// Initializes a new instance of the class. - /// - /// The unique key for this port. - /// The signal type supported by this port. - /// The physical connection type of this port. - /// An object used to refer to this port in the parent device's ExecuteSwitch method. - /// The device this port belongs to. - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent) - : this(key, type, connType, selector, parent, false) + public class RoutingOutputPort : RoutingPort { - } + /// + /// The IRoutingOutputs object this port lives on. + /// + [JsonIgnore] + public IRoutingOutputs ParentDevice { get; private set; } - /// - /// Initializes a new instance of the class, potentially marking it as internal. - /// - /// The unique key for this port. - /// The signal type supported by this port. - /// The physical connection type of this port. - /// An object used to refer to this port in the parent device's ExecuteSwitch method. - /// The device this port belongs to. - /// True if this port represents an internal connection within a device (e.g., card to backplane). - public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, - object selector, IRoutingOutputs parent, bool isInternal) - : base(key, type, connType, selector, isInternal) + /// + /// Tracks which destinations are currently using this output port. + /// + public InUseTracking InUseTracker { get; private set; } + + + /// + /// Initializes a new instance of the class. + /// + /// The unique key for this port. + /// The signal type supported by this port. + /// The physical connection type of this port. + /// An object used to refer to this port in the parent device's ExecuteSwitch method. + /// The device this port belongs to. + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + /// + /// Initializes a new instance of the class, potentially marking it as internal. + /// + /// The unique key for this port. + /// The signal type supported by this port. + /// The physical connection type of this port. + /// An object used to refer to this port in the parent device's ExecuteSwitch method. + /// The device this port belongs to. + /// True if this port represents an internal connection within a device (e.g., card to backplane). + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + object selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + /// + /// Returns a string representation of the output port. + /// + /// A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType". + public override string ToString() + { + return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; + } + } + + /*public class RoutingOutputPort : RoutingPort { - ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); - InUseTracker = new InUseTracking(); - } + /// + /// Gets or sets the ParentDevice + /// + public IRoutingOutputs ParentDevice { get; private set; } - /// - /// Returns a string representation of the output port. - /// - /// A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType". - public override string ToString() - { - return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}"; - } -} + /// + /// Gets or sets the InUseTracker + /// + public InUseTracking InUseTracker { get; private set; } + + /// + /// + /// An object used to refer to this port in the IRouting device's ExecuteSwitch method. + /// May be string, number, whatever + /// The IRoutingOutputs object this port lives on + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent) + : this(key, type, connType, selector, parent, false) + { + } + + public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, + TSelector selector, IRoutingOutputs parent, bool isInternal) + : base(key, type, connType, selector, isInternal) + { + ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent)); + InUseTracker = new InUseTracking(); + } + + /// + /// ToString method + /// + /// + public override string ToString() + { + return ParentDevice.Key + ":" + Key; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs index 7fa01811..30440578 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPort.cs @@ -1,12 +1,12 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// Base class for and . -/// -public abstract class RoutingPort : IKeyed +namespace PepperDash.Essentials.Core +{ + /// + /// Base class for and . + /// + public abstract class RoutingPort : IKeyed { /// /// The unique key identifying this port within its parent device. @@ -28,14 +28,14 @@ public abstract class RoutingPort : IKeyed /// Indicates if this port represents an internal connection within a device (e.g., card to backplane). /// public bool IsInternal { get; private set; } - /// - /// An object used to match feedback values to this port, if applicable. - /// - public object FeedbackMatchObject { get; set; } - /// - /// A reference to the underlying hardware port object (e.g., SimplSharpPro Port), if applicable. - /// - public object Port { get; set; } + /// + /// An object used to match feedback values to this port, if applicable. + /// + public object FeedbackMatchObject { get; set; } + /// + /// A reference to the underlying hardware port object (e.g., SimplSharpPro Port), if applicable. + /// + public object Port { get; set; } /// /// Initializes a new instance of the class. @@ -53,5 +53,43 @@ public abstract class RoutingPort : IKeyed Selector = selector; IsInternal = isInternal; } -} + } + /*public abstract class RoutingPort:IKeyed + { + /// + /// Gets or sets the Key + /// + public string Key { get; private set; } + /// + /// Gets or sets the Type + /// + public eRoutingSignalType Type { get; private set; } + /// + /// Gets or sets the ConnectionType + /// + public eRoutingPortConnectionType ConnectionType { get; private set; } + public readonly TSelector Selector; + /// + /// Gets or sets the IsInternal + /// + public bool IsInternal { get; private set; } + /// + /// Gets or sets the FeedbackMatchObject + /// + public object FeedbackMatchObject { get; set; } + /// + /// Gets or sets the Port + /// + public object Port { get; set; } + + public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, TSelector selector, bool isInternal) + { + Key = key; + Type = type; + ConnectionType = connType; + Selector = selector; + IsInternal = isInternal; + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs index d78021b4..7ee349b5 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPortCollection.cs @@ -1,104 +1,40 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace PepperDash.Essentials.Core; +using System.Linq; +namespace PepperDash.Essentials.Core +{ /// - /// Represents a RoutingPortCollection, which behaves like a List with an indexer for case-insensitive lookup of - /// ports by their key names. Enforces unique port keys (case-insensitive) - attempting to add a port whose key - /// already exists in the collection throws an . + /// Represents a RoutingPortCollection, which is essentially a List with an indexer for case-insensitive lookup of ports by their key names. /// - /// - /// Derives from rather than so key-uniqueness can be enforced - /// via the virtual Insert/Set/Remove/Clear hooks (List{T}'s members are not virtual). Public surface area - /// (int indexer, Add/Insert/Remove/Clear, collection-initializer support, enumeration) is preserved; an - /// method is provided for source compatibility with existing List{T}.AddRange call sites. - /// - public class RoutingPortCollection : Collection where T: RoutingPort + public class RoutingPortCollection : List where T: RoutingPort { - private readonly Dictionary _portsByKey = new Dictionary(StringComparer.OrdinalIgnoreCase); - /// /// Case-insensitive port lookup linked to ports' keys /// - public T this[string key] + public T this[string key] { get { - return key != null && _portsByKey.TryGetValue(key, out var port) ? port : null; - } - } - - /// - /// Adds a range of ports to the collection. Provided for source compatibility with List{T}.AddRange. - /// - /// The ports to add. - public void AddRange(IEnumerable items) - { - if (items == null) - throw new ArgumentNullException(nameof(items)); - - foreach (var item in items) - { - Add(item); - } - } - - /// - protected override void InsertItem(int index, T item) - { - ValidateNewItem(item); - base.InsertItem(index, item); - _portsByKey[item.Key] = item; - } - - /// - protected override void SetItem(int index, T item) - { - ValidateNewItem(item, indexBeingReplaced: index); - var replaced = Items[index]; - base.SetItem(index, item); - if (replaced != null && !string.Equals(replaced.Key, item.Key, StringComparison.OrdinalIgnoreCase)) - { - _portsByKey.Remove(replaced.Key); - } - _portsByKey[item.Key] = item; - } - - /// - protected override void RemoveItem(int index) - { - var removed = Items[index]; - base.RemoveItem(index); - if (removed != null) - { - _portsByKey.Remove(removed.Key); - } - } - - /// - protected override void ClearItems() - { - base.ClearItems(); - _portsByKey.Clear(); - } - - private void ValidateNewItem(T item, int? indexBeingReplaced = null) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - if (string.IsNullOrEmpty(item.Key)) - throw new ArgumentException("RoutingPort must have a non-empty Key", nameof(item)); - - if (_portsByKey.TryGetValue(item.Key, out var existing)) - { - var existingIndex = Items.IndexOf(existing); - if (existingIndex != indexBeingReplaced) - { - throw new ArgumentException( - $"A RoutingPort with key '{item.Key}' already exists in this RoutingPortCollection", nameof(item)); - } + return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); } } } + +/* /// + /// Basically a List , with an indexer to find ports by key name + /// + public class RoutingPortCollection : List where T : RoutingPort + { + /// + /// Case-insensitive port lookup linked to ports' keys + /// + public T this[string key] + { + get + { + return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + } + } + }*/ +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs b/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs index a783a0b0..96a2ca32 100644 --- a/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs +++ b/src/PepperDash.Essentials.Core/Routing/RoutingPortNames.cs @@ -4,248 +4,249 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Defines constant string values for common routing port keys. -/// These should correspond directly with the portNames var in the config tool. + /// These should correspond directly with the portNames var in the config tool. /// public class RoutingPortNames { - /// - /// antennaIn - /// + /// + /// antennaIn + /// public const string AntennaIn = "antennaIn"; - /// - /// anyAudioIn - /// + /// + /// anyAudioIn + /// public const string AnyAudioIn = "anyAudioIn"; - /// - /// anyAudioOut - /// + /// + /// anyAudioOut + /// public const string AnyAudioOut = "anyAudioOut"; - /// - /// anyOut - /// - public const string AnyOut = "anyOut"; - /// - /// anyVideoIn - /// - public const string AnyVideoIn = "anyVideoIn"; - /// - /// anyVideoOut - /// + /// + /// anyOut + /// + public const string AnyOut = "anyOut"; + /// + /// anyVideoIn + /// + public const string AnyVideoIn = "anyVideoIn"; + /// + /// anyVideoOut + /// public const string AnyVideoOut = "anyVideoOut"; - /// - /// balancedAudioOut - /// + /// + /// balancedAudioOut + /// public const string BalancedAudioOut = "balancedAudioOut"; - /// - /// codecOsd - /// - public const string CodecOsd = "codecOsd"; - /// - /// componentIn - /// + /// + /// codecOsd + /// + public const string CodecOsd = "codecOsd"; + /// + /// componentIn + /// public const string ComponentIn = "componentIn"; - /// - /// componentOut - /// + /// + /// componentOut + /// public const string ComponentOut = "componentOut"; - /// - /// compositeIn - /// + /// + /// compositeIn + /// public const string CompositeIn = "compositeIn"; - /// - /// compositeOut - /// + /// + /// compositeOut + /// public const string CompositeOut = "compositeOut"; - /// - /// displayPortIn - /// + /// + /// displayPortIn + /// public const string DisplayPortIn = "displayPortIn"; - /// - /// displayPortIn1 - /// + /// + /// displayPortIn1 + /// public const string DisplayPortIn1 = "displayPortIn1"; - /// - /// displayPortIn2 - /// + /// + /// displayPortIn2 + /// public const string DisplayPortIn2 = "displayPortIn2"; - /// - /// displayPortIn3 - /// + /// + /// displayPortIn3 + /// public const string DisplayPortIn3 = "displayPortIn3"; - /// - /// displayPortOut - /// + /// + /// displayPortOut + /// public const string DisplayPortOut = "displayPortOut"; - /// - /// dmIn - /// + /// + /// dmIn + /// public const string DmIn = "dmIn"; - /// - /// dmOut - /// + /// + /// dmOut + /// public const string DmOut = "dmOut"; - /// - /// dviIn - /// + /// + /// dviIn + /// public const string DviIn = "dviIn"; - /// - /// dviIn1 - /// - public const string DviIn1 = "dviIn1"; - /// - /// dviOut - /// - public const string DviOut = "dviOut"; - /// - /// hdmiIn - /// + /// + /// dviIn1 + /// + public const string DviIn1 = "dviIn1"; + /// + /// dviOut + /// + public const string DviOut = "dviOut"; + /// + /// hdmiIn + /// public const string HdmiIn = "hdmiIn"; - /// - /// hdmiIn1 - /// + /// + /// hdmiIn1 + /// public const string HdmiIn1 = "hdmiIn1"; - /// - /// hdmiIn1PC - /// - public const string HdmiIn1PC = "hdmiIn1PC"; - /// - /// hdmiIn2 - /// + /// + /// hdmiIn1PC + /// + public const string HdmiIn1PC = "hdmiIn1PC"; + /// + /// hdmiIn2 + /// public const string HdmiIn2 = "hdmiIn2"; - /// - /// hdmiIn2PC - /// - public const string HdmiIn2PC = "hdmiIn2PC"; - /// - /// hdmiIn3 - /// + /// + /// hdmiIn2PC + /// + public const string HdmiIn2PC = "hdmiIn2PC"; + /// + /// hdmiIn3 + /// public const string HdmiIn3 = "hdmiIn3"; - /// - /// hdmiIn4 - /// + /// + /// hdmiIn4 + /// public const string HdmiIn4 = "hdmiIn4"; - /// - /// hdmiIn5 - /// + /// + /// hdmiIn5 + /// public const string HdmiIn5 = "hdmiIn5"; - /// - /// hdmiIn6 - /// + /// + /// hdmiIn6 + /// public const string HdmiIn6 = "hdmiIn6"; - /// - /// hdmiOut - /// + /// + /// hdmiOut + /// public const string HdmiOut = "hdmiOut"; - /// - /// hdmiOut1 - /// - public const string HdmiOut1 = "hdmiOut1"; - /// - /// hdmiOut2 - /// - public const string HdmiOut2 = "hdmiOut2"; - /// - /// hdmiOut3 - /// - public const string HdmiOut3 = "hdmiOut3"; - /// - /// hdmiOut4 - /// - public const string HdmiOut4 = "hdmiOut4"; - /// - /// hdmiOut5 - /// - public const string HdmiOut5 = "hdmiOut5"; - /// - /// hdmiOut6 - /// - public const string HdmiOut6 = "hdmiOut6"; - /// - /// none - /// - public const string None = "none"; - /// - /// rgbIn - /// + /// + /// hdmiOut1 + /// + public const string HdmiOut1 = "hdmiOut1"; + /// + /// hdmiOut2 + /// + public const string HdmiOut2 = "hdmiOut2"; + /// + /// hdmiOut3 + /// + public const string HdmiOut3 = "hdmiOut3"; + /// + /// hdmiOut4 + /// + public const string HdmiOut4 = "hdmiOut4"; + /// + /// hdmiOut5 + /// + public const string HdmiOut5 = "hdmiOut5"; + /// + /// hdmiOut6 + /// + public const string HdmiOut6 = "hdmiOut6"; + /// + /// none + /// + public const string None = "none"; + /// + /// rgbIn + /// public const string RgbIn = "rgbIn"; - /// - /// rgbIn1 - /// - public const string RgbIn1 = "rgbIn1"; - /// - /// rgbIn2 - /// - public const string RgbIn2 = "rgbIn2"; - /// - /// vgaIn - /// + /// + /// rgbIn1 + /// + public const string RgbIn1 = "rgbIn1"; + /// + /// rgbIn2 + /// + public const string RgbIn2 = "rgbIn2"; + /// + /// vgaIn + /// public const string VgaIn = "vgaIn"; - /// - /// vgaIn1 - /// - public const string VgaIn1 = "vgaIn1"; - /// - /// vgaOut - /// + /// + /// vgaIn1 + /// + public const string VgaIn1 = "vgaIn1"; + /// + /// vgaOut + /// public const string VgaOut = "vgaOut"; - /// - /// IPC/OPS - /// - public const string IpcOps = "ipcOps"; - /// - /// MediaPlayer - /// - public const string MediaPlayer = "mediaPlayer"; - /// - /// UsbCIn - /// - public const string UsbCIn = "usbCIn"; - /// - /// UsbCIn1 - /// - public const string UsbCIn1 = "usbCIn1"; - /// - /// UsbCIn2 - /// - public const string UsbCIn2 = "usbCIn2"; - /// - /// UsbCIn3 - /// - public const string UsbCIn3 = "usbCIn3"; - /// - /// UsbCOut - /// - public const string UsbCOut = "usbCOut"; - /// - /// UsbCOut1 - /// - public const string UsbCOut1 = "usbCOut1"; - /// - /// UsbCOut2 - /// - public const string UsbCOut2 = "usbCOut2"; - /// - /// UsbCOut3 - /// - public const string UsbCOut3 = "usbCOut3"; - /// - /// HdBaseTIn - /// - public const string HdBaseTIn = "hdBaseTIn"; - /// - /// HdBaseTOut - /// - public const string HdBaseTOut = "hdBaseTOut"; - /// - /// SdiIn - /// - public const string SdiIn = "sdiIn"; - /// - /// SdiOut - /// - public const string SdiOut = "sdiOut"; + /// + /// IPC/OPS + /// + public const string IpcOps = "ipcOps"; + /// + /// MediaPlayer + /// + public const string MediaPlayer = "mediaPlayer"; + /// + /// UsbCIn + /// + public const string UsbCIn = "usbCIn"; + /// + /// UsbCIn1 + /// + public const string UsbCIn1 = "usbCIn1"; + /// + /// UsbCIn2 + /// + public const string UsbCIn2 = "usbCIn2"; + /// + /// UsbCIn3 + /// + public const string UsbCIn3 = "usbCIn3"; + /// + /// UsbCOut + /// + public const string UsbCOut = "usbCOut"; + /// + /// UsbCOut1 + /// + public const string UsbCOut1 = "usbCOut1"; + /// + /// UsbCOut2 + /// + public const string UsbCOut2 = "usbCOut2"; + /// + /// UsbCOut3 + /// + public const string UsbCOut3 = "usbCOut3"; + /// + /// HdBaseTIn + /// + public const string HdBaseTIn = "hdBaseTIn"; + /// + /// HdBaseTOut + /// + public const string HdBaseTOut = "hdBaseTOut"; + /// + /// SdiIn + /// + public const string SdiIn = "sdiIn"; + /// + /// SdiOut + /// + public const string SdiOut = "sdiOut"; + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/TieLine.cs b/src/PepperDash.Essentials.Core/Routing/TieLine.cs index 8b2f8863..87470eeb 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLine.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLine.cs @@ -1,153 +1,155 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; +using Newtonsoft.Json; -namespace PepperDash.Essentials.Core; - -/// -/// Represents a connection (tie line) between a and a . -/// -public class TieLine +namespace PepperDash.Essentials.Core { /// /// Represents a connection between routing ports, linking a source output port to a destination input port. /// This class is used to define signal paths for routing algorithms, including signal type overrides and internal connections. /// - public RoutingOutputPort SourcePort { get; private set; } - /// - /// The destination input port of the tie line. - /// - public RoutingInputPort DestinationPort { get; private set; } - //public int InUseCount { get { return DestinationUsingThis.Count; } } + public class TieLine + { + /// + /// The source output port of the tie line. + /// + public RoutingOutputPort SourcePort { get; private set; } + /// + /// The destination input port of the tie line. + /// + public RoutingInputPort DestinationPort { get; private set; } + //public int InUseCount { get { return DestinationUsingThis.Count; } } + + /// + /// Gets the type of this tie line. Returns the intersection of signal types supported by both + /// the source and destination ports (what signals can actually travel through this tie line), + /// or the OverrideType when it is set. + /// + public eRoutingSignalType Type + { + get + { + if (OverrideType.HasValue) return OverrideType.Value; + return SourcePort.Type & DestinationPort.Type; + } + } + + /// + /// Use this to override the Type property. For example, when both ports support AudioVideo + /// but the physical cable only carries Audio or Video, setting this will limit the signal + /// paths available to the routing algorithm without affecting the actual port types. + /// When set, this value is used instead of the calculated intersection of source and destination types. + /// + public eRoutingSignalType? OverrideType { get; set; } + + //List DestinationUsingThis = new List(); + + /// + /// Gets a value indicating whether this tie line represents an internal connection within a device (both source and destination ports are internal). + /// + public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } } + /// + /// Gets a value indicating whether the signal types of the source and destination ports differ. + /// + public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } } + /// + /// Gets a value indicating whether the connection types of the source and destination ports differ. + /// + public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } } + /// + /// A descriptive note about any type mismatch, if applicable. + /// + public string TypeMismatchNote { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The source output port. + /// The destination input port. + public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort) + { + if (sourcePort == null || destinationPort == null) + throw new ArgumentNullException("source or destination port"); + SourcePort = sourcePort; + DestinationPort = destinationPort; + } + + /// + /// Creates a tie line with an overriding Type. See help for OverrideType property for info. + /// + /// The source output port. + /// The destination input port. + /// The signal type to limit the link to. Overrides the calculated intersection of port types for routing calculations. + public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) : + this(sourcePort, destinationPort) + { + OverrideType = overrideType; + } + + /// + /// Creates a tie line with an overriding Type. See help for OverrideType property for info. + /// + /// The source output port. + /// The destination input port. + /// The signal type to limit the link to. Overrides the calculated intersection of port types for routing calculations. + public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) : + this(sourcePort, destinationPort) + { + OverrideType = overrideType; + } + + /// + /// Will link up video status from supporting inputs to connected outputs. + /// + public void Activate() + { + // Now does nothing + } + + /// + /// Deactivates the tie line. + /// + public void Deactivate() + { + // Now does nothing + } + + /// + /// Returns a string representation of the tie line. + /// + /// A string describing the source, destination, and type of the tie line. + public override string ToString() + { + return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key, + DestinationPort.ParentDevice.Key, DestinationPort.Key, Type.ToString()); + } + } + + //******************************************************************************** /// /// Represents a collection of objects, which define signal paths for routing algorithms. /// This class provides functionality for managing tie lines and includes a singleton instance for global access. /// - public eRoutingSignalType Type + public class TieLineCollection : List { - get + /// + /// Gets the default singleton instance of the . + /// + public static TieLineCollection Default { - if (OverrideType.HasValue) return OverrideType.Value; - return DestinationPort.Type; + get + { + if (_Default == null) + _Default = new TieLineCollection(); + return _Default; + } } + + /// + /// Backing field for the singleton instance. + /// + [JsonIgnore] + private static TieLineCollection _Default; } - - /// - /// Use this to override the Type property for the destination port. For example, - /// when the tie line is type AudioVideo, and the signal flow should be limited to - /// Audio-only or Video only, changing this type will alter the signal paths - /// available to the routing algorithm without affecting the actual Type - /// of the destination port. - /// - public eRoutingSignalType? OverrideType { get; set; } - - //List DestinationUsingThis = new List(); - - /// - /// Gets a value indicating whether this tie line represents an internal connection within a device (both source and destination ports are internal). - /// - public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } } - /// - /// Gets a value indicating whether the signal types of the source and destination ports differ. - /// - public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } } - /// - /// Gets a value indicating whether the connection types of the source and destination ports differ. - /// - public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } } - /// - /// A descriptive note about any type mismatch, if applicable. - /// - public string TypeMismatchNote { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The source output port. - /// The destination input port. - public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort) - { - if (sourcePort == null || destinationPort == null) - throw new ArgumentNullException("source or destination port"); - SourcePort = sourcePort; - DestinationPort = destinationPort; - } - - /// - /// Creates a tie line with an overriding Type. See help for OverrideType property for info. - /// - /// The source output port. - /// The destination input port. - /// The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations. - public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) : - this(sourcePort, destinationPort) - { - OverrideType = overrideType; - } - - /// - /// Creates a tie line with an overriding Type. See help for OverrideType property for info. - /// - /// The source output port. - /// The destination input port. - /// The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations. - public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) : - this(sourcePort, destinationPort) - { - OverrideType = overrideType; - } - - /// - /// Will link up video status from supporting inputs to connected outputs. - /// - public void Activate() - { - // Now does nothing - } - - /// - /// Deactivates the tie line. - /// - public void Deactivate() - { - // Now does nothing - } - - /// - /// Returns a string representation of the tie line. - /// - /// A string describing the source, destination, and type of the tie line. - public override string ToString() - { - return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key, - DestinationPort.ParentDevice.Key, DestinationPort.Key, Type.ToString()); - } -} - -//******************************************************************************** - -/// -/// Represents a collection of objects. -/// -public class TieLineCollection : List -{ - /// - /// Gets the default singleton instance of the . - /// - public static TieLineCollection Default - { - get - { - if (_Default == null) - _Default = new TieLineCollection(); - return _Default; - } - } - - /// - /// Backing field for the singleton instance. - /// - [JsonIgnore] - private static TieLineCollection _Default; } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs index 3ea456d3..606cb603 100644 --- a/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs +++ b/src/PepperDash.Essentials.Core/Routing/TieLineConfig.cs @@ -11,8 +11,8 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.Config; - +namespace PepperDash.Essentials.Core.Config +{ /// /// Represents the configuration data for a single tie line between two routing ports. /// @@ -49,7 +49,9 @@ namespace PepperDash.Essentials.Core.Config; public string DestinationPort { get; set; } /// - /// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations. + /// Optional override for the signal type of the tie line. If set, this overrides the calculated + /// intersection of source and destination port types for routing calculations. Useful when the + /// physical cable supports fewer signal types than both ports are capable of. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringEnumConverter))] @@ -96,6 +98,32 @@ namespace PepperDash.Essentials.Core.Config; return null; } + // Validate signal type compatibility + if (OverrideType.HasValue) + { + // When override type is specified, both ports must support it + if (!sourceOutputPort.Type.HasFlag(OverrideType.Value)) + { + LogError($"Override type '{OverrideType.Value}' is not supported by source port '{SourcePort}' (type: {sourceOutputPort.Type})"); + return null; + } + + if (!destinationInputPort.Type.HasFlag(OverrideType.Value)) + { + LogError($"Override type '{OverrideType.Value}' is not supported by destination port '{DestinationPort}' (type: {destinationInputPort.Type})"); + return null; + } + } + else + { + // Without override type, ports must have at least one common signal type flag + if ((sourceOutputPort.Type & destinationInputPort.Type) == 0) + { + LogError($"Incompatible signal types: source port '{SourcePort}' (type: {sourceOutputPort.Type}) has no common signal types with destination port '{DestinationPort}' (type: {destinationInputPort.Type})"); + return null; + } + } + return new TieLine(sourceOutputPort, destinationInputPort, OverrideType); } @@ -116,4 +144,5 @@ namespace PepperDash.Essentials.Core.Config; { return $"{SourceKey}.{SourceCard}.{SourcePort} --> {DestinationKey}.{DestinationCard}.{DestinationPort}"; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs index 33332856..9245dd48 100644 --- a/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingPortConnectionType.cs @@ -100,4 +100,4 @@ /// HdBaseT } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs index f3c346dc..708b7448 100644 --- a/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs +++ b/src/PepperDash.Essentials.Core/Routing/eRoutingSignalType.cs @@ -27,7 +27,19 @@ namespace PepperDash.Essentials.Core /// /// Control signal type /// - Usb = 8, + [Obsolete("UsbOutput is no longer supported and will be removed in a future release.")] + UsbOutput = 8, + /// + /// Control signal type + /// + [Obsolete("UsbInput is no longer supported and will be removed in a future release.")] + UsbInput = 16, + + /// + /// Secondary audio signal type + /// + [Obsolete("SecondaryAudio is no longer supported and will be removed in a future release.")] + SecondaryAudio = 32 } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/CrestronGlobalSecretsProvider.cs b/src/PepperDash.Essentials.Core/Secrets/CrestronGlobalSecretsProvider.cs index d89efd2c..45983b0c 100644 --- a/src/PepperDash.Essentials.Core/Secrets/CrestronGlobalSecretsProvider.cs +++ b/src/PepperDash.Essentials.Core/Secrets/CrestronGlobalSecretsProvider.cs @@ -5,97 +5,115 @@ using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -public class CrestronGlobalSecretsProvider : ISecretProvider +namespace PepperDash.Essentials.Core { - public string Key { get; set; } - //Added for reference - public string Description { get; private set; } - - public CrestronGlobalSecretsProvider(string key) - { - Key = key; - Description = String.Format("Default secret provider serving all local applications"); - - } - - static CrestronGlobalSecretsProvider() - { - //Added for future encrypted reference - var secureSupported = CrestronSecureStorage.Supported; - - CrestronDataStoreStatic.InitCrestronDataStore(); - if (secureSupported) - { - //doThingsFuture - } - } - /// - /// Set secret for item in the CrestronSecretsProvider + /// Represents a CrestronGlobalSecretsProvider /// - /// Secret Key - /// Secret Value - public bool SetSecret(string key, object value) + public class CrestronGlobalSecretsProvider : ISecretProvider { - var secret = value as string; - CrestronDataStore.CDS_ERROR returnCode; + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + //Added for reference + /// + /// Gets or sets the Description + /// + public string Description { get; private set; } - if (String.IsNullOrEmpty(secret)) + /// + /// Constructor for CrestronGlobalSecretsProvider + /// + /// The key for the secret provider + public CrestronGlobalSecretsProvider(string key) { - returnCode = CrestronDataStoreStatic.clearGlobal(key); - if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + Key = key; + Description = String.Format("Default secret provider serving all local applications"); + + } + + static CrestronGlobalSecretsProvider() + { + //Added for future encrypted reference + var secureSupported = CrestronSecureStorage.Supported; + + CrestronDataStoreStatic.InitCrestronDataStore(); + if (secureSupported) { - Debug.LogMessage(LogEventLevel.Information, this, "Successfully removed secret \"{0}\"", secret); - return true; + //doThingsFuture } } - else + /// + /// Set secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// Secret Value + public bool SetSecret(string key, object value) { - returnCode = CrestronDataStoreStatic.SetGlobalStringValue(key, secret); - if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + var secret = value as string; + CrestronDataStore.CDS_ERROR returnCode; + + if (String.IsNullOrEmpty(secret)) { - Debug.LogMessage(LogEventLevel.Information, this, "Successfully set secret \"{0}\"", secret); - return true; + returnCode = CrestronDataStoreStatic.clearGlobal(key); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.LogMessage(LogEventLevel.Information, this, "Successfully removed secret \"{0}\"", secret); + return true; + } + } + + else + { + returnCode = CrestronDataStoreStatic.SetGlobalStringValue(key, secret); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.LogMessage(LogEventLevel.Information, this, "Successfully set secret \"{0}\"", secret); + return true; + } + } + + Debug.LogMessage(LogEventLevel.Information, this, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); + return false; + } + + /// + /// Retrieve secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// ISecret Object containing key, provider, and value + + + + public ISecret GetSecret(string key) + { + string mySecret; + var getErrorCode = CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret); + + switch (getErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.LogMessage(LogEventLevel.Verbose, this, "Secret Successfully retrieved for {0}:{1}", Key, key); + return new CrestronSecret(key, mySecret, this); + default: + Debug.LogMessage(LogEventLevel.Information, this, "Unable to retrieve secret for {0}:{1} - {2}", + Key, key, getErrorCode.ToString()); + return null; } } - Debug.LogMessage(LogEventLevel.Information, this, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); - return false; - } - - /// - /// Retrieve secret for item in the CrestronSecretsProvider - /// - /// Secret Key - /// ISecret Object containing key, provider, and value - public ISecret GetSecret(string key) - { - string mySecret; - var getErrorCode = CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret); - - switch (getErrorCode) + /// + /// Determine if a secret is present within the provider without retrieving it + /// + /// Secret Key + /// bool if present + public bool TestSecret(string key) { - case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: - Debug.LogMessage(LogEventLevel.Verbose, this, "Secret Successfully retrieved for {0}:{1}", Key, key); - return new CrestronSecret(key, mySecret, this); - default: - Debug.LogMessage(LogEventLevel.Information, this, "Unable to retrieve secret for {0}:{1} - {2}", - Key, key, getErrorCode.ToString()); - return null; + string mySecret; + return CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; } } - /// - /// Determine if a secret is present within the provider without retrieving it - /// - /// Secret Key - /// bool if present - public bool TestSecret(string key) - { - string mySecret; - return CrestronDataStoreStatic.GetGlobalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/CrestronLocalSecretsProvider.cs b/src/PepperDash.Essentials.Core/Secrets/CrestronLocalSecretsProvider.cs index 0bffd23e..31a1faa3 100644 --- a/src/PepperDash.Essentials.Core/Secrets/CrestronLocalSecretsProvider.cs +++ b/src/PepperDash.Essentials.Core/Secrets/CrestronLocalSecretsProvider.cs @@ -6,97 +6,117 @@ using Crestron.SimplSharpPro; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -public class CrestronLocalSecretsProvider : ISecretProvider +namespace PepperDash.Essentials.Core { - public string Key { get; set; } - //Added for reference - public string Description { get; private set; } - - - public CrestronLocalSecretsProvider(string key) - { - Key = key; - Description = String.Format("Default secret provider serving Essentials Application {0}", InitialParametersClass.ApplicationNumber); - } - - static CrestronLocalSecretsProvider() - { - //Added for future encrypted reference - var secureSupported = CrestronSecureStorage.Supported; - - CrestronDataStoreStatic.InitCrestronDataStore(); - if (secureSupported) - { - //doThingsFuture - } - } - /// - /// Set secret for item in the CrestronSecretsProvider + /// Represents a CrestronLocalSecretsProvider /// - /// Secret Key - /// Secret Value - public bool SetSecret(string key, object value) + public class CrestronLocalSecretsProvider : ISecretProvider { - var secret = value as string; - CrestronDataStore.CDS_ERROR returnCode; + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + //Added for reference + /// + /// Gets or sets the Description + /// + public string Description { get; private set; } - if (String.IsNullOrEmpty(secret)) + /// + /// Constructor for CrestronLocalSecretsProvider + /// + /// The key for the secret provider + public CrestronLocalSecretsProvider(string key) { - returnCode = CrestronDataStoreStatic.clearLocal(key); - if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + Key = key; + Description = String.Format("Default secret provider serving Essentials Application {0}", InitialParametersClass.ApplicationNumber); + } + + static CrestronLocalSecretsProvider() + { + //Added for future encrypted reference + var secureSupported = CrestronSecureStorage.Supported; + + CrestronDataStoreStatic.InitCrestronDataStore(); + if (secureSupported) { - Debug.LogMessage(LogEventLevel.Information, this, "Successfully removed secret \"{0}\"", secret); - return true; + //doThingsFuture } } - else + /// + /// Set secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// Secret Value + public bool SetSecret(string key, object value) { - returnCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret); - if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + var secret = value as string; + CrestronDataStore.CDS_ERROR returnCode; + + if (String.IsNullOrEmpty(secret)) { - Debug.LogMessage(LogEventLevel.Information, this, "Successfully set secret \"{0}\"", secret); - return true; + returnCode = CrestronDataStoreStatic.clearLocal(key); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.LogMessage(LogEventLevel.Information, this, "Successfully removed secret \"{0}\"", secret); + return true; + } + } + + else + { + returnCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret); + if (returnCode == CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + Debug.LogMessage(LogEventLevel.Information, this, "Successfully set secret \"{0}\"", secret); + return true; + } + } + + Debug.LogMessage(LogEventLevel.Information, this, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); + return false; + } + + /// + /// Retrieve secret for item in the CrestronSecretsProvider + /// + /// Secret Key + /// ISecret Object containing key, provider, and value + /// + /// GetSecret method + /// + public ISecret GetSecret(string key) + { + string mySecret; + var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret); + + switch (getErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.LogMessage(LogEventLevel.Verbose, this, "Secret Successfully retrieved for {0}:{1}", Key, key); + return new CrestronSecret(key, mySecret, this); + default: + Debug.LogMessage(LogEventLevel.Information, this, "Unable to retrieve secret for {0}:{1} - {2}", + Key, key, getErrorCode.ToString()); + return null; } } - Debug.LogMessage(LogEventLevel.Information, this, "Unable to set secret for {0}:{1} - {2}", Key, key, returnCode.ToString()); - return false; - } - - /// - /// Retrieve secret for item in the CrestronSecretsProvider - /// - /// Secret Key - /// ISecret Object containing key, provider, and value - public ISecret GetSecret(string key) - { - string mySecret; - var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret); - - switch (getErrorCode) + /// + /// Determine if a secret is present within the provider without retrieving it + /// + /// Secret Key + /// bool if present + /// + /// TestSecret method + /// + public bool TestSecret(string key) { - case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: - Debug.LogMessage(LogEventLevel.Verbose, this, "Secret Successfully retrieved for {0}:{1}", Key, key); - return new CrestronSecret(key, mySecret, this); - default: - Debug.LogMessage(LogEventLevel.Information, this, "Unable to retrieve secret for {0}:{1} - {2}", - Key, key, getErrorCode.ToString()); - return null; + string mySecret; + return CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; } } - /// - /// Determine if a secret is present within the provider without retrieving it - /// - /// Secret Key - /// bool if present - public bool TestSecret(string key) - { - string mySecret; - return CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret) == CrestronDataStore.CDS_ERROR.CDS_SUCCESS; - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/CrestronSecret.cs b/src/PepperDash.Essentials.Core/Secrets/CrestronSecret.cs index 1b628a44..5a606453 100644 --- a/src/PepperDash.Essentials.Core/Secrets/CrestronSecret.cs +++ b/src/PepperDash.Essentials.Core/Secrets/CrestronSecret.cs @@ -4,23 +4,41 @@ using System.Linq; using System.Text; using Crestron.SimplSharp; -namespace PepperDash.Essentials.Core; - -/// -/// Special container class for CrestronSecret provider -/// -public class CrestronSecret : ISecret +namespace PepperDash.Essentials.Core { - public ISecretProvider Provider { get; private set; } - public string Key { get; private set; } - - public object Value { get; private set; } - - public CrestronSecret(string key, string value, ISecretProvider provider) + /// + /// Special container class for CrestronSecret provider + /// + public class CrestronSecret : ISecret { - Key = key; - Value = value; - Provider = provider; + /// + /// Gets the Provider + /// + public ISecretProvider Provider { get; private set; } + + /// + /// Gets the Key + /// + public string Key { get; private set; } + + /// + /// Gets the Value + /// + public object Value { get; private set; } + + /// + /// Constructor for CrestronSecret + /// + /// key for the secret + /// value of the secret + /// provider of the secret + public CrestronSecret(string key, string value, ISecretProvider provider) + { + Key = key; + Value = value; + Provider = provider; + } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/Interfaces.cs b/src/PepperDash.Essentials.Core/Secrets/Interfaces.cs index f0b7fd38..55ce5ccc 100644 --- a/src/PepperDash.Essentials.Core/Secrets/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Secrets/Interfaces.cs @@ -1,57 +1,58 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Core; - -/// -/// All ISecrecretProvider classes must implement this interface. -/// -public interface ISecretProvider : IKeyed +namespace PepperDash.Essentials.Core { /// - /// Set secret value for provider by key + /// Defines the contract for ISecretProvider /// - /// key of secret to set - /// value to set secret to - /// - bool SetSecret(string key, object value); + public interface ISecretProvider : IKeyed + { + /// + /// Set secret value for provider by key + /// + /// key of secret to set + /// value to set secret to + /// + bool SetSecret(string key, object value); + + /// + /// Return object containing secret from provider + /// + /// key of secret to retrieve + /// + ISecret GetSecret(string key); + + /// + /// Verifies presence of secret + /// + /// key of secret to chek + /// + bool TestSecret(string key); + + /// + /// Description of the secrets provider + /// + string Description { get; } + } /// - /// Return object containing secret from provider + /// interface for delivering secrets in Essentials. /// - /// key of secret to retrieve - /// - ISecret GetSecret(string key); + public interface ISecret + { + /// + /// Instance of ISecretProvider that the secret belongs to + /// + ISecretProvider Provider { get; } - /// - /// Verifies presence of secret - /// - /// key of secret to chek - /// - bool TestSecret(string key); + /// + /// Key of the secret in the provider + /// + string Key { get; } - /// - /// Description of the secrets provider - /// - string Description { get; } -} - -/// -/// interface for delivering secrets in Essentials. -/// -public interface ISecret -{ - /// - /// Instance of ISecretProvider that the secret belongs to - /// - ISecretProvider Provider { get; } - - /// - /// Key of the secret in the provider - /// - string Key { get; } - - /// - /// Value of the secret - /// - object Value { get; } + /// + /// Value of the secret + /// + object Value { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs b/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs index f35866fd..ac4e80d8 100644 --- a/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs +++ b/src/PepperDash.Essentials.Core/Secrets/SecretsManager.cs @@ -3,21 +3,27 @@ using System.Collections.Generic; using System.Linq; using Crestron.SimplSharp; using PepperDash.Core; +using PepperDash.Core.Logging; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -public static class SecretsManager +namespace PepperDash.Essentials.Core { - public static Dictionary Secrets { get; private set; } - /// - /// Initialize the SecretsManager + /// SecretsManager static class /// - public static void Initialize() + public static class SecretsManager { - try + /// + /// Gets the Secrets dictionary + /// + public static Dictionary Secrets { get; private set; } + + /// + /// Initialize method + /// + public static void Initialize() { + AddSecretProvider("default", new CrestronLocalSecretsProvider("default")); AddSecretProvider("CrestronGlobalSecrets", new CrestronGlobalSecretsProvider("CrestronGlobalSecrets")); @@ -42,338 +48,336 @@ public static class SecretsManager "Return data about secrets provider", ConsoleAccessLevelEnum.AccessAdministrator); } - catch (Exception e) + + static SecretsManager() { - Debug.LogError(e, "SecretsManager Initialize failed"); - } - } - - static SecretsManager() - { - Secrets = new Dictionary(); - } - - /// - /// Get Secret Provider from dictionary by key - /// - /// Dictionary Key for provider - /// ISecretProvider - public static ISecretProvider GetSecretProviderByKey(string key) - { - ISecretProvider secret; - - Secrets.TryGetValue(key, out secret); - - if (secret == null) - { - Debug.LogMessage(LogEventLevel.Debug, "SecretsManager unable to retrieve SecretProvider with the key '{0}'", key); - } - return secret; - } - - public static void GetProviderInfo(string cmd) - { - string response; - var args = cmd.Split(' '); - - if (cmd.Length == 0 || (args.Length == 1 && args[0] == "?")) - { - response = "Returns data about secrets provider. Format 'secretproviderinfo '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - + Secrets = new Dictionary(); } - if (args.Length == 1) + /// + /// Get Secret Provider from dictionary by key + /// + /// Dictionary Key for provider + /// ISecretProvider + /// + /// GetSecretProviderByKey method + /// + public static ISecretProvider GetSecretProviderByKey(string key) { - var provider = GetSecretProviderByKey(args[0]); + ISecretProvider secret; - if (provider == null) + Secrets.TryGetValue(key, out secret); + + if (secret == null) { - response = "Invalid secrets provider key"; + Debug.LogMessage(LogEventLevel.Debug, "SecretsManager unable to retrieve SecretProvider with the key '{0}'", key); + } + return secret; + } + + /// + /// GetProviderInfo method + /// + public static void GetProviderInfo(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (cmd.Length == 0 || (args.Length == 1 && args[0] == "?")) + { + response = "Returns data about secrets provider. Format 'secretproviderinfo '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1) + { + var provider = GetSecretProviderByKey(args[0]); + + if (provider == null) + { + response = "Invalid secrets provider key"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + response = String.Format("{0} : {1}", provider.Key, provider.Description); CrestronConsole.ConsoleCommandResponse(response); return; } - response = String.Format("{0} : {1}", provider.Key, provider.Description); - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - response = "Improper number of arguments"; - CrestronConsole.ConsoleCommandResponse(response); - - } - - - /// - /// Console Command that returns all valid secrets in the essentials program. - /// - /// - public static void ListProviders(string cmd) - { - var response = String.Empty; - var args = cmd.Split(' '); - - if (cmd.Length == 0) - { - if (Secrets != null && Secrets.Count > 0) - { - response = Secrets.Aggregate(response, - (current, secretProvider) => current + (secretProvider.Key + "\n\r")); - } - else - { - response = "No Secrets Providers Available"; - } - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - if (args.Length == 1 && args[0] == "?") - { - response = "Reports all valid and preset Secret providers"; - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - - response = "Improper number of arguments"; - CrestronConsole.ConsoleCommandResponse(response); - - } - - /// - /// Add secret provider to secrets dictionary - /// - /// Key of new entry - /// New Provider Entry - public static void AddSecretProvider(string key, ISecretProvider provider) - { - if (!Secrets.ContainsKey(key)) - { - Secrets.Add(key, provider); - Debug.LogMessage(LogEventLevel.Debug, "Secrets provider '{0}' added to SecretsManager", key); - return; - } - Debug.LogMessage(LogEventLevel.Information, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key ); - } - - /// - /// Add secret provider to secrets dictionary, with optional overwrite parameter - /// - /// Key of new entry - /// New provider entry - /// true to overwrite any existing providers in the dictionary - public static void AddSecretProvider(string key, ISecretProvider provider, bool overwrite) - { - if (!Secrets.ContainsKey(key)) - { - Secrets.Add(key, provider); - Debug.LogMessage(LogEventLevel.Debug, "Secrets provider '{0}' added to SecretsManager", key); - return; - } - if (overwrite) - { - Secrets.Add(key, provider); - Debug.LogMessage(LogEventLevel.Debug, "Provider with the key '{0}' already exists in secrets. Overwriting with new secrets provider.", key); - return; - } - Debug.LogMessage(LogEventLevel.Information, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key); - } - - private static void SetSecretProcess(string cmd) - { - string response; - var args = cmd.Split(' '); - - if (args.Length == 0) - { - //some Instructional Text - response = "Adds secrets to secret provider. Format 'setsecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - if (args.Length == 1 && args[0] == "?") - { - response = "Adds secrets to secret provider. Format 'setsecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - if (args.Length < 3) - { - response = "Improper number of arguments"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - var provider = GetSecretProviderByKey(args[0]); - - if (provider == null) - { - //someFail - response = "Provider key invalid"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - var key = args[1]; - var secret = args[2]; - - CrestronConsole.ConsoleCommandResponse(SetSecret(provider, key, secret)); - } - - private static void UpdateSecretProcess(string cmd) - { - string response; - var args = cmd.Split(' '); - - if (args.Length == 0) - { - //some Instructional Text - response = "Updates secrets in secret provider. Format 'updatesecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - if (args.Length == 1 && args[0] == "?") - { - response = "Updates secrets in secret provider. Format 'updatesecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - - if (args.Length < 3) - { - //someFail response = "Improper number of arguments"; CrestronConsole.ConsoleCommandResponse(response); - return; } - var provider = GetSecretProviderByKey(args[0]); - if (provider == null) + /// + /// Console Command that returns all valid secrets in the essentials program. + /// + /// + /// + /// ListProviders method + /// + public static void ListProviders(string cmd) { - //someFail - response = "Provider key invalid"; + var response = String.Empty; + var args = cmd.Split(' '); + + if (cmd.Length == 0) + { + if (Secrets != null && Secrets.Count > 0) + { + response = Secrets.Aggregate(response, + (current, secretProvider) => current + (secretProvider.Key + "\n\r")); + } + else + { + response = "No Secrets Providers Available"; + } + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Reports all valid and preset Secret providers"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + + } + + /// + /// Add secret provider to secrets dictionary + /// + /// Key of new entry + /// New Provider Entry + /// + /// AddSecretProvider method + /// + public static void AddSecretProvider(string key, ISecretProvider provider) + { + if (!Secrets.ContainsKey(key)) + { + Secrets.Add(key, provider); + Debug.LogMessage(LogEventLevel.Debug, "Secrets provider '{0}' added to SecretsManager", key); + return; + } + Debug.LogMessage(LogEventLevel.Information, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key ); + } + + /// + /// Add secret provider to secrets dictionary, with optional overwrite parameter + /// + /// Key of new entry + /// New provider entry + /// true to overwrite any existing providers in the dictionary + /// + /// AddSecretProvider method + /// + public static void AddSecretProvider(string key, ISecretProvider provider, bool overwrite) + { + if (!Secrets.ContainsKey(key)) + { + Secrets.Add(key, provider); + Debug.LogMessage(LogEventLevel.Debug, "Secrets provider '{0}' added to SecretsManager", key); + return; + } + if (overwrite) + { + Secrets.Add(key, provider); + Debug.LogMessage(LogEventLevel.Debug, "Provider with the key '{0}' already exists in secrets. Overwriting with new secrets provider.", key); + return; + } + Debug.LogMessage(LogEventLevel.Information, "Unable to add Provider '{0}' to Secrets. Provider with that key already exists", key); + } + + private static void SetSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Adds secrets to secret provider. Format 'setsecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Adds secrets to secret provider. Format 'setsecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + if (args.Length < 3) + { + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = GetSecretProviderByKey(args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + var secret = args[2]; + + CrestronConsole.ConsoleCommandResponse(SetSecret(provider, key, secret)); + } + + private static void UpdateSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Updates secrets in secret provider. Format 'updatesecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Updates secrets in secret provider. Format 'updatesecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + if (args.Length < 3) + { + //someFail + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = GetSecretProviderByKey(args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + var secret = args[2]; + + CrestronConsole.ConsoleCommandResponse(UpdateSecret(provider, key, secret)); + + } + + private static string UpdateSecret(ISecretProvider provider, string key, string secret) + { + var secretPresent = provider.TestSecret(key); + + provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + + if (!secretPresent) + return + $"Unable to update secret for {provider.Key}:{key} - Please use the 'SetSecret' command to modify it"; + var response = provider.SetSecret(key, secret) + ? $"Secret successfully set for {provider.Key}:{key}" + : $"Unable to set secret for {provider.Key}:{key}"; + return response; + } + + private static string SetSecret(ISecretProvider provider, string key, string secret) + { + var secretPresent = provider.TestSecret(key); + + provider.LogVerbose("SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); + + if (secretPresent) + return + $"Unable to set secret for {provider.Key}:{key} - Please use the 'UpdateSecret' command to modify it"; + var response = provider.SetSecret(key, secret) + ? $"Secret successfully set for {provider.Key}:{key}" + : $"Unable to set secret for {provider.Key}:{key}"; + return response; + + } + + private static void DeleteSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Deletes secrets in secret provider. Format 'deletesecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + if (args.Length == 1 && args[0] == "?") + { + response = "Deletes secrets in secret provider. Format 'deletesecret '"; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + + if (args.Length < 2) + { + //someFail + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = GetSecretProviderByKey(args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + + provider.SetSecret(key, ""); + response = provider.SetSecret(key, "") + ? $"Secret successfully deleted for {provider.Key}:{key}" + : $"Unable to delete secret for {provider.Key}:{key}"; CrestronConsole.ConsoleCommandResponse(response); return; + } - - var key = args[1]; - var secret = args[2]; - - CrestronConsole.ConsoleCommandResponse(UpdateSecret(provider, key, secret)); - } - private static string UpdateSecret(ISecretProvider provider, string key, string secret) - { - var secretPresent = provider.TestSecret(key); - Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); - - if (!secretPresent) - return - String.Format( - "Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to modify it"); - var response = provider.SetSecret(key, secret) - ? String.Format( - "Secret successfully set for {0}:{1}", - provider.Key, key) - : String.Format( - "Unable to set secret for {0}:{1}", - provider.Key, key); - return response; - } - - private static string SetSecret(ISecretProvider provider, string key, string secret) - { - var secretPresent = provider.TestSecret(key); - - Debug.LogMessage(LogEventLevel.Verbose, provider, "SecretsProvider {0} {1} contain a secret entry for {2}", provider.Key, secretPresent ? "does" : "does not", key); - - if (secretPresent) - return - String.Format( - "Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it"); - var response = provider.SetSecret(key, secret) - ? String.Format( - "Secret successfully set for {0}:{1}", - provider.Key, key) - : String.Format( - "Unable to set secret for {0}:{1}", - provider.Key, key); - return response; - - } - - private static void DeleteSecretProcess(string cmd) - { - string response; - var args = cmd.Split(' '); - - if (args.Length == 0) - { - //some Instructional Text - response = "Deletes secrets in secret provider. Format 'deletesecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - if (args.Length == 1 && args[0] == "?") - { - response = "Deletes secrets in secret provider. Format 'deletesecret '"; - CrestronConsole.ConsoleCommandResponse(response); - return; - } - - - - if (args.Length < 2) - { - //someFail - response = "Improper number of arguments"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - var provider = GetSecretProviderByKey(args[0]); - - if (provider == null) - { - //someFail - response = "Provider key invalid"; - CrestronConsole.ConsoleCommandResponse(response); - return; - - } - - var key = args[1]; - - - provider.SetSecret(key, ""); - response = provider.SetSecret(key, "") - ? String.Format( - "Secret successfully deleted for {0}:{1}", - provider.Key, key) - : String.Format( - "Unable to delete secret for {0}:{1}", - provider.Key, key); - CrestronConsole.ConsoleCommandResponse(response); - return; - - - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Secrets/SecretsPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Secrets/SecretsPropertiesConfig.cs index b447851a..a5a76074 100644 --- a/src/PepperDash.Essentials.Core/Secrets/SecretsPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Secrets/SecretsPropertiesConfig.cs @@ -7,15 +7,23 @@ using System.Text; using Crestron.SimplSharp; using Newtonsoft.Json; -namespace PepperDash.Essentials.Core; - -/// -/// Provide a way to easily deserialize into a secret object from config -/// -public class SecretsPropertiesConfig +namespace PepperDash.Essentials.Core { - [JsonProperty("provider")] - public string Provider { get; set; } - [JsonProperty("key")] - public string Key { get; set; } + /// + /// Provide a way to easily deserialize into a secret object from config + /// + public class SecretsPropertiesConfig + { + /// + /// Gets or sets the Provider + /// + [JsonProperty("provider")] + public string Provider { get; set; } + + /// + /// Gets or sets the Key + /// + [JsonProperty("key")] + public string Key { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Shades/Shade Interfaces.cs b/src/PepperDash.Essentials.Core/Shades/Shade Interfaces.cs index 08abeaa8..2fb5ffd7 100644 --- a/src/PepperDash.Essentials.Core/Shades/Shade Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Shades/Shade Interfaces.cs @@ -1,177 +1,178 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Core.Shades; - -/// -/// Defines the contract for IShades -/// -public interface IShades +namespace PepperDash.Essentials.Core.Shades { - /// - /// List of shades controlled by this device - /// - List Shades { get; } -} + /// + /// Defines the contract for IShades + /// + public interface IShades + { + /// + /// List of shades controlled by this device + /// + List Shades { get; } + } -/// -/// Requirements for a device that implements basic Open/Close/Stop shade control (Uses 3 relays) -/// -public interface IShadesOpenCloseStop -{ - /// - /// Opens the shade - /// - void Open(); + /// + /// Requirements for a device that implements basic Open/Close/Stop shade control (Uses 3 relays) + /// + public interface IShadesOpenCloseStop + { + /// + /// Opens the shade + /// + void Open(); + + /// + /// Closes the shade + /// + void Close(); + + /// + /// Stops the shade + /// + void Stop(); + } /// - /// Closes the shade + /// Requirements for a device that implements Open/Close/Stop shade control with presets /// - void Close(); + public interface IShadesOpenClosePreset : IShadesOpenCloseStop + { + /// + /// Recalls the preset + /// + /// preset number to recall + void RecallPreset(uint presetNumber); + + /// + /// Saves the preset + /// + /// preset number to save + void SavePreset(uint presetNumber); + + /// + /// Label for the preset button + /// + string StopOrPresetButtonLabel { get; } + + /// + /// Event raised when a preset is recalled + /// + event EventHandler PresetSaved; + } + + + /// + /// Defines the contract for IShadesRaiseLowerFeedback + /// + public interface IShadesRaiseLowerFeedback + { + /// + /// Feedback to indicate if the shade is lowering + /// + BoolFeedback ShadeIsLoweringFeedback { get; } + + /// + /// Feedback to indicate if the shade is raising + /// + BoolFeedback ShadeIsRaisingFeedback { get; } + } /// - /// Stops the shade + /// Requirements for a shade/scene that is open or closed /// - void Stop(); -} + public interface IShadesOpenClosedFeedback: IShadesOpenCloseStop + { + /// + /// Feedback to indicate if the shade is open + /// + BoolFeedback ShadeIsOpenFeedback { get; } -/// -/// Requirements for a device that implements Open/Close/Stop shade control with presets -/// -public interface IShadesOpenClosePreset : IShadesOpenCloseStop -{ - /// - /// Recalls the preset - /// - /// preset number to recall - void RecallPreset(uint presetNumber); + /// + /// Feedback to indicate if the shade is closed + /// + BoolFeedback ShadeIsClosedFeedback { get; } + } /// - /// Saves the preset + /// Used to implement raise/stop/lower/stop from single button /// - /// preset number to save - void SavePreset(uint presetNumber); + public interface IShadesStopOrMove + { + /// + /// Raises the shade or stops it if it's already moving + /// + void OpenOrStop(); + + /// + /// Lowers the shade or stops it if it's already moving + /// + void CloseOrStop(); + + /// + /// Opens, closes, or stops the shade depending on current state + /// + void OpenCloseOrStop(); + } + + /// + /// Defines the contract for IShadesStopFeedback + /// + public interface IShadesStopFeedback : IShadesOpenCloseStop + { + /// + /// Feedback to indicate if the shade is stopped + /// + BoolFeedback IsStoppedFeedback { get; } + } + + /// + /// Requirements for position + /// + public interface IShadesPosition + { + /// + /// Gets the current position of the shade + /// + /// value of the position to set + void SetPosition(ushort value); + } /// - /// Label for the preset button + /// Basic feedback for shades position /// - string StopOrPresetButtonLabel { get; } + public interface IShadesFeedback: IShadesPosition, IShadesStopFeedback + { + /// + /// Feedback to indicate the current position of the shade + /// + IntFeedback PositionFeedback { get; } + } /// - /// Event raised when a preset is recalled + /// Feedback for scenes /// - event EventHandler PresetSaved; -} + public interface ISceneFeedback + { + /// + /// Runs the scene + /// + void Run(); - -/// -/// Defines the contract for IShadesRaiseLowerFeedback -/// -public interface IShadesRaiseLowerFeedback -{ - /// - /// Feedback to indicate if the shade is lowering - /// - BoolFeedback ShadeIsLoweringFeedback { get; } + /// + /// Feedback to indicate if all shades are at the scene position + /// + BoolFeedback AllAreAtSceneFeedback { get; } + } /// - /// Feedback to indicate if the shade is raising + /// Combines basic shade interfaces for Crestron Basic shades /// - BoolFeedback ShadeIsRaisingFeedback { get; } -} + public interface ICrestronBasicShade : IShadesOpenClosedFeedback, + IShadesStopOrMove, IShadesFeedback, IShadesRaiseLowerFeedback + { -/// -/// Requirements for a shade/scene that is open or closed -/// -public interface IShadesOpenClosedFeedback : IShadesOpenCloseStop -{ - /// - /// Feedback to indicate if the shade is open - /// - BoolFeedback ShadeIsOpenFeedback { get; } - - /// - /// Feedback to indicate if the shade is closed - /// - BoolFeedback ShadeIsClosedFeedback { get; } -} - -/// -/// Used to implement raise/stop/lower/stop from single button -/// -public interface IShadesStopOrMove -{ - /// - /// Raises the shade or stops it if it's already moving - /// - void OpenOrStop(); - - /// - /// Lowers the shade or stops it if it's already moving - /// - void CloseOrStop(); - - /// - /// Opens, closes, or stops the shade depending on current state - /// - void OpenCloseOrStop(); -} - -/// -/// Defines the contract for IShadesStopFeedback -/// -public interface IShadesStopFeedback : IShadesOpenCloseStop -{ - /// - /// Feedback to indicate if the shade is stopped - /// - BoolFeedback IsStoppedFeedback { get; } -} - -/// -/// Requirements for position -/// -public interface IShadesPosition -{ - /// - /// Gets the current position of the shade - /// - /// value of the position to set - void SetPosition(ushort value); -} - -/// -/// Basic feedback for shades position -/// -public interface IShadesFeedback : IShadesPosition, IShadesStopFeedback -{ - /// - /// Feedback to indicate the current position of the shade - /// - IntFeedback PositionFeedback { get; } -} - -/// -/// Feedback for scenes -/// -public interface ISceneFeedback -{ - /// - /// Runs the scene - /// - void Run(); - - /// - /// Feedback to indicate if all shades are at the scene position - /// - BoolFeedback AllAreAtSceneFeedback { get; } -} - -/// -/// Combines basic shade interfaces for Crestron Basic shades -/// -public interface ICrestronBasicShade : IShadesOpenClosedFeedback, - IShadesStopOrMove, IShadesFeedback, IShadesRaiseLowerFeedback -{ - -} + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Shades/ShadeBase.cs b/src/PepperDash.Essentials.Core/Shades/ShadeBase.cs new file mode 100644 index 00000000..ce5de98d --- /dev/null +++ b/src/PepperDash.Essentials.Core/Shades/ShadeBase.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using PepperDash.Core; +using PepperDash.Essentials.Core.CrestronIO; + +namespace PepperDash.Essentials.Core.Shades +{ + /// + /// Base class for shades + /// + [Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")] + public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop + { + /// + /// Constructor + /// + /// key of the shade device + /// name of the shade device + public ShadeBase(string key, string name) + : base(key, name) + { + + } + + #region iShadesOpenClose Members + + /// + /// Opens the shade + /// + public abstract void Open(); + + /// + /// Stops the shade + /// + public abstract void Stop(); + + /// + /// Closes the shade + /// + public abstract void Close(); + + #endregion + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SigHelper.cs b/src/PepperDash.Essentials.Core/SigHelper.cs index 3aa1bbc8..df1ad605 100644 --- a/src/PepperDash.Essentials.Core/SigHelper.cs +++ b/src/PepperDash.Essentials.Core/SigHelper.cs @@ -7,8 +7,8 @@ using Crestron.SimplSharpPro; using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Helper class for various Sig events /// @@ -75,4 +75,5 @@ namespace PepperDash.Essentials.Core; { sig.CreateRamp(level, time / 10); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDPad.cs b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDPad.cs index 011b3f97..605b2344 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDPad.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDPad.cs @@ -6,45 +6,46 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core.SmartObjects; - -/// -/// Represents a SmartObjectDPad -/// -public class SmartObjectDPad : SmartObjectHelperBase +namespace PepperDash.Essentials.Core.SmartObjects { - /// - /// Gets or sets the SigUp - /// - public BoolOutputSig SigUp { get { return GetBoolOutputNamed("Up"); } } + /// + /// Represents a SmartObjectDPad + /// + public class SmartObjectDPad : SmartObjectHelperBase + { + /// + /// Gets or sets the SigUp + /// + public BoolOutputSig SigUp { get { return GetBoolOutputNamed("Up"); } } - /// - /// Gets or sets the SigDown - /// - public BoolOutputSig SigDown { get { return GetBoolOutputNamed("Down"); } } + /// + /// Gets or sets the SigDown + /// + public BoolOutputSig SigDown { get { return GetBoolOutputNamed("Down"); } } - /// - /// Gets or sets the SigLeft - /// - public BoolOutputSig SigLeft { get { return GetBoolOutputNamed("Left"); } } + /// + /// Gets or sets the SigLeft + /// + public BoolOutputSig SigLeft { get { return GetBoolOutputNamed("Left"); } } - /// - /// Gets or sets the SigRight - /// - public BoolOutputSig SigRight { get { return GetBoolOutputNamed("Right"); } } + /// + /// Gets or sets the SigRight + /// + public BoolOutputSig SigRight { get { return GetBoolOutputNamed("Right"); } } - /// - /// Gets or sets the SigCenter - /// - public BoolOutputSig SigCenter { get { return GetBoolOutputNamed("Center"); } } + /// + /// Gets or sets the SigCenter + /// + public BoolOutputSig SigCenter { get { return GetBoolOutputNamed("Center"); } } - /// - /// Constructor - /// - /// smart object - /// use user object handler if true - public SmartObjectDPad(SmartObject so, bool useUserObjectHandler) - : base(so, useUserObjectHandler) - { - } + /// + /// Constructor + /// + /// smart object + /// use user object handler if true + public SmartObjectDPad(SmartObject so, bool useUserObjectHandler) + : base(so, useUserObjectHandler) + { + } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs index d83d3aa5..fdf7dc04 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs @@ -1,148 +1,153 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Text; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; + using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.SmartObjects; - -/// -/// Represents a SmartObjectDynamicList -/// -public class SmartObjectDynamicList : SmartObjectHelperBase +namespace PepperDash.Essentials.Core.SmartObjects { - /// - /// Sig name for Scroll To Item - /// - public const string SigNameScrollToItem = "Scroll To Item"; - - /// - /// Sig name for Set Number of Items - /// - public const string SigNameSetNumberOfItems = "Set Number of Items"; - - /// - /// Gets or sets the NameSigOffset - /// - public uint NameSigOffset { get; private set; } - - /// - /// Gets or sets the Count - /// - public ushort Count + /// + /// Represents a SmartObjectDynamicList + /// + public class SmartObjectDynamicList : SmartObjectHelperBase { - get + /// + /// Sig name for Scroll To Item + /// + public const string SigNameScrollToItem = "Scroll To Item"; + + /// + /// Sig name for Set Number of Items + /// + public const string SigNameSetNumberOfItems = "Set Number of Items"; + + /// + /// Gets or sets the NameSigOffset + /// + public uint NameSigOffset { get; private set; } + + /// + /// Gets or sets the Count + /// + public ushort Count { - return SmartObject.UShortInput[SigNameSetNumberOfItems].UShortValue; + get + { + return SmartObject.UShortInput[SigNameSetNumberOfItems].UShortValue; + } + set { SmartObject.UShortInput[SigNameSetNumberOfItems].UShortValue = value; } } - set { SmartObject.UShortInput[SigNameSetNumberOfItems].UShortValue = value; } - } - /// - /// Gets or sets the MaxCount - /// - public int MaxCount { get; private set; } + /// + /// Gets or sets the MaxCount + /// + public int MaxCount { get; private set; } - /// - /// Wrapper for smart object - /// - /// - /// True if the standard user object action handler will be used - /// The starting join of the string sigs for the button labels - public SmartObjectDynamicList(SmartObject so, bool useUserObjectHandler, uint nameSigOffset) : base(so, useUserObjectHandler) - { - try + /// + /// Wrapper for smart object + /// + /// + /// True if the standard user object action handler will be used + /// The starting join of the string sigs for the button labels + public SmartObjectDynamicList(SmartObject so, bool useUserObjectHandler, uint nameSigOffset) : base(so, useUserObjectHandler) + { + try + { + // Just try to touch the count signal to make sure this is indeed a dynamic list + var c = Count; + NameSigOffset = nameSigOffset; + MaxCount = SmartObject.BooleanOutput.Count(s => s.Name.EndsWith("Pressed")); + //Debug.LogMessage(LogEventLevel.Verbose, "Smart object {0} has {1} max", so.ID, MaxCount); + } + catch + { + var msg = string.Format("SmartObjectDynamicList: Smart Object {0:X2}-{1} is not a dynamic list. Ignoring", so.Device.ID, so.ID); + Debug.LogMessage(LogEventLevel.Information, msg); + } + } + + /// + /// SetItem method + /// + public void SetItem(uint index, string mainText, string iconName, Action action) { - // Just try to touch the count signal to make sure this is indeed a dynamic list - var c = Count; - NameSigOffset = nameSigOffset; - MaxCount = SmartObject.BooleanOutput.Count(s => s.Name.EndsWith("Pressed")); - //Debug.LogMessage(LogEventLevel.Verbose, "Smart object {0} has {1} max", so.ID, MaxCount); + SetItemMainText(index, mainText); + SetItemIcon(index, iconName); + SetItemButtonAction(index, action); + //try + //{ + // SetMainButtonText(index, text); + // SetIcon(index, iconName); + // SetButtonAction(index, action); + //} + //catch(Exception e) + //{ + // Debug.LogMessage(LogEventLevel.Debug, "Cannot set Dynamic List item {0} on smart object {1}", index, SmartObject.ID); + // ErrorLog.Warn(e.ToString()); + //} } - catch + + /// + /// SetItemMainText method + /// + public void SetItemMainText(uint index, string text) { - var msg = string.Format("SmartObjectDynamicList: Smart Object {0:X2}-{1} is not a dynamic list. Ignoring", so.Device.ID, so.ID); - Debug.LogMessage(LogEventLevel.Information, msg); + if (index > MaxCount) return; + // The list item template defines CIPS tags that refer to standard joins + (SmartObject.Device as BasicTriList).StringInput[NameSigOffset + index].StringValue = text; } - } - /// - /// SetItem method - /// - public void SetItem(uint index, string mainText, string iconName, Action action) - { - SetItemMainText(index, mainText); - SetItemIcon(index, iconName); - SetItemButtonAction(index, action); - //try - //{ - // SetMainButtonText(index, text); - // SetIcon(index, iconName); - // SetButtonAction(index, action); - //} - //catch(Exception e) - //{ - // Debug.LogMessage(LogEventLevel.Debug, "Cannot set Dynamic List item {0} on smart object {1}", index, SmartObject.ID); - // ErrorLog.Warn(e.ToString()); - //} - } + /// + /// SetItemIcon method + /// + public void SetItemIcon(uint index, string iconName) + { + if (index > MaxCount) return; + SmartObject.StringInput[string.Format("Set Item {0} Icon Serial", index)].StringValue = iconName; + } - /// - /// SetItemMainText method - /// - public void SetItemMainText(uint index, string text) - { - if (index > MaxCount) return; - // The list item template defines CIPS tags that refer to standard joins - (SmartObject.Device as BasicTriList).StringInput[NameSigOffset + index].StringValue = text; - } + /// + /// SetItemButtonAction method + /// + public void SetItemButtonAction(uint index, Action action) + { + if (index > MaxCount) return; + SmartObject.BooleanOutput[string.Format("Item {0} Pressed", index)].UserObject = action; + } - /// - /// SetItemIcon method - /// - public void SetItemIcon(uint index, string iconName) - { - if (index > MaxCount) return; - SmartObject.StringInput[string.Format("Set Item {0} Icon Serial", index)].StringValue = iconName; - } + /// + /// SetFeedback method + /// + public void SetFeedback(uint index, bool interlocked) + { + if (interlocked) + ClearFeedbacks(); + SmartObject.BooleanInput[string.Format("Item {0} Selected", index)].BoolValue = true; + } - /// - /// SetItemButtonAction method - /// - public void SetItemButtonAction(uint index, Action action) - { - if (index > MaxCount) return; - SmartObject.BooleanOutput[string.Format("Item {0} Pressed", index)].UserObject = action; - } + /// + /// ClearFeedbacks method + /// + public void ClearFeedbacks() + { + for(int i = 1; i<= Count; i++) + SmartObject.BooleanInput[string.Format("Item {0} Selected", i)].BoolValue = false; + } - /// - /// SetFeedback method - /// - public void SetFeedback(uint index, bool interlocked) - { - if (interlocked) - ClearFeedbacks(); - SmartObject.BooleanInput[string.Format("Item {0} Selected", index)].BoolValue = true; - } - - /// - /// ClearFeedbacks method - /// - public void ClearFeedbacks() - { - for (int i = 1; i <= Count; i++) - SmartObject.BooleanInput[string.Format("Item {0} Selected", i)].BoolValue = false; - } - - /// - /// Removes Action object from all buttons - /// - public void ClearActions() - { - Debug.LogMessage(LogEventLevel.Verbose, "SO CLEAR"); - for (ushort i = 1; i <= MaxCount; i++) - SmartObject.BooleanOutput[string.Format("Item {0} Pressed", i)].UserObject = null; + /// + /// Removes Action object from all buttons + /// + public void ClearActions() + { + Debug.LogMessage(LogEventLevel.Verbose, "SO CLEAR"); + for(ushort i = 1; i <= MaxCount; i++) + SmartObject.BooleanOutput[string.Format("Item {0} Pressed", i)].UserObject = null; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectHelperBase.cs b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectHelperBase.cs index aca01f2c..a86687aa 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectHelperBase.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectHelperBase.cs @@ -9,91 +9,96 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core.SmartObjects; -/// -/// Represents a SmartObjectHelperBase -/// -public class SmartObjectHelperBase +namespace PepperDash.Essentials.Core.SmartObjects { /// - /// Gets or sets the SmartObject + /// Represents a SmartObjectHelperBase /// - public SmartObject SmartObject { get; private set; } - - /// - /// Gets or sets the Validated - /// - public bool Validated { get; protected set; } - - /// - /// Constructor - /// - /// smart object - /// use the user object hadnler if true - public SmartObjectHelperBase(SmartObject so, bool useUserObjectHandler) + public class SmartObjectHelperBase { - SmartObject = so; - if (useUserObjectHandler) + /// + /// Gets or sets the SmartObject + /// + public SmartObject SmartObject { get; private set; } + + /// + /// Gets or sets the Validated + /// + public bool Validated { get; protected set; } + + /// + /// Constructor + /// + /// smart object + /// use the user object hadnler if true + public SmartObjectHelperBase(SmartObject so, bool useUserObjectHandler) + { + SmartObject = so; + if (useUserObjectHandler) + { + // Prevent this from double-registering + SmartObject.SigChange -= this.SmartObject_SigChange; + SmartObject.SigChange += this.SmartObject_SigChange; + } + } + + /// + /// Destructor + /// + ~SmartObjectHelperBase() { - // Prevent this from double-registering SmartObject.SigChange -= this.SmartObject_SigChange; - SmartObject.SigChange += this.SmartObject_SigChange; } - } - /// - /// Destructor - /// - ~SmartObjectHelperBase() - { - SmartObject.SigChange -= this.SmartObject_SigChange; - } - - /// - /// Helper to get a sig name with debugging when fail - /// - /// - /// - public BoolOutputSig GetBoolOutputNamed(string name) - { - if (SmartObject.BooleanOutput.Contains(name)) - return SmartObject.BooleanOutput[name]; - else - Debug.LogMessage(LogEventLevel.Information, "WARNING: Cannot get signal. Smart object {0} on trilist {1:x2} does not contain signal '{2}'", - SmartObject.ID, SmartObject.Device.ID, name); - return null; - } - - /// - /// Sets action on signal after checking for existence. - /// - /// - /// - public void SetBoolAction(string name, Action a) - { - if (SmartObject.BooleanOutput.Contains(name)) - SmartObject.BooleanOutput[name].UserObject = a; - else + /// + /// Helper to get a sig name with debugging when fail + /// + /// + /// + public BoolOutputSig GetBoolOutputNamed(string name) { - Debug.LogMessage(LogEventLevel.Information, "WARNING: Cannot set action. Smart object {0} on trilist {1:x2} does not contain signal '{2}'", - SmartObject.ID, SmartObject.Device.ID, name); + if (SmartObject.BooleanOutput.Contains(name)) + return SmartObject.BooleanOutput[name]; + else + Debug.LogMessage(LogEventLevel.Information, "WARNING: Cannot get signal. Smart object {0} on trilist {1:x2} does not contain signal '{2}'", + SmartObject.ID, SmartObject.Device.ID, name); + return null; } - } - /// - /// Standard Action listener - /// - /// - /// - void SmartObject_SigChange(GenericBase currentDevice, SmartObjectEventArgs args) - { - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); - } + /// + /// Sets action on signal after checking for existence. + /// + /// + /// + /// + /// SetBoolAction method + /// + public void SetBoolAction(string name, Action a) + { + if (SmartObject.BooleanOutput.Contains(name)) + SmartObject.BooleanOutput[name].UserObject = a; + else + { + Debug.LogMessage(LogEventLevel.Information, "WARNING: Cannot set action. Smart object {0} on trilist {1:x2} does not contain signal '{2}'", + SmartObject.ID, SmartObject.Device.ID, name); + } + } + /// + /// Standard Action listener + /// + /// + /// + void SmartObject_SigChange(GenericBase currentDevice, SmartObjectEventArgs args) + { + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectNumeric.cs b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectNumeric.cs index 8dbd1998..2b6e06b8 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectNumeric.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectNumeric.cs @@ -6,94 +6,92 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core.SmartObjects; - -/// -/// Represents a SmartObjectNumeric -/// This is a helper for a numeric keypad. It has the digits 0-9 and two miscellaneous buttons. The Misc1SigName and Misc2SigName can be set to whatever the user wants, but default to "Misc_1" and "Misc_2". -/// -public class SmartObjectNumeric : SmartObjectHelperBase +namespace PepperDash.Essentials.Core.SmartObjects { /// - /// Defaults to "Misc_1". The name of the button in VTPro (Usually the text) + /// Represents a SmartObjectNumeric /// - public string Misc1SigName { get; set; } - - - /// - /// Gets or sets the Misc2SigName - /// - public string Misc2SigName { get; set; } - - /// - /// Gets or sets the Digit1 - /// - public BoolOutputSig Digit1 { get { return GetBoolOutputNamed("1"); } } - - /// - /// Gets or sets the Digit2 - /// - public BoolOutputSig Digit2 { get { return GetBoolOutputNamed("2"); } } - - /// - /// Gets or sets the Digit3 - /// - public BoolOutputSig Digit3 { get { return GetBoolOutputNamed("3"); } } - - /// - /// Gets or sets the Digit4 - /// - public BoolOutputSig Digit4 { get { return GetBoolOutputNamed("4"); } } - - /// - /// Gets or sets the Digit5 - /// - public BoolOutputSig Digit5 { get { return GetBoolOutputNamed("5"); } } - - /// - /// Gets or sets the Digit6 - /// - public BoolOutputSig Digit6 { get { return GetBoolOutputNamed("6"); } } - - /// - /// Gets or sets the Digit7 - /// - public BoolOutputSig Digit7 { get { return GetBoolOutputNamed("7"); } } - - /// - /// Gets or sets the Digit8 - /// - public BoolOutputSig Digit8 { get { return GetBoolOutputNamed("8"); } } - - /// - /// Gets or sets the Digit9 - /// - public BoolOutputSig Digit9 { get { return GetBoolOutputNamed("9"); } } - - /// - /// Gets or sets the Digit0 - /// - public BoolOutputSig Digit0 { get { return GetBoolOutputNamed("0"); } } - - /// - /// Gets or sets the Misc1 - /// - public BoolOutputSig Misc1 { get { return GetBoolOutputNamed(Misc1SigName); } } - - /// - /// Gets or sets the Misc2 - /// - public BoolOutputSig Misc2 { get { return GetBoolOutputNamed(Misc2SigName); } } - - /// - /// Constructor - /// - /// smart object - /// use user handler if true - public SmartObjectNumeric(SmartObject so, bool useUserObjectHandler) : base(so, useUserObjectHandler) + public class SmartObjectNumeric : SmartObjectHelperBase { - Misc1SigName = "Misc_1"; - Misc2SigName = "Misc_2"; - } -} + /// + /// Gets or sets the Misc1SigName + /// + public string Misc1SigName { get; set; } + /// + /// Gets or sets the Misc2SigName + /// + public string Misc2SigName { get; set; } + + /// + /// Gets or sets the Digit1 + /// + public BoolOutputSig Digit1 { get { return GetBoolOutputNamed("1"); } } + + /// + /// Gets or sets the Digit2 + /// + public BoolOutputSig Digit2 { get { return GetBoolOutputNamed("2"); } } + + /// + /// Gets or sets the Digit3 + /// + public BoolOutputSig Digit3 { get { return GetBoolOutputNamed("3"); } } + + /// + /// Gets or sets the Digit4 + /// + public BoolOutputSig Digit4 { get { return GetBoolOutputNamed("4"); } } + + /// + /// Gets or sets the Digit5 + /// + public BoolOutputSig Digit5 { get { return GetBoolOutputNamed("5"); } } + + /// + /// Gets or sets the Digit6 + /// + public BoolOutputSig Digit6 { get { return GetBoolOutputNamed("6"); } } + + /// + /// Gets or sets the Digit7 + /// + public BoolOutputSig Digit7 { get { return GetBoolOutputNamed("7"); } } + + /// + /// Gets or sets the Digit8 + /// + public BoolOutputSig Digit8 { get { return GetBoolOutputNamed("8"); } } + + /// + /// Gets or sets the Digit9 + /// + public BoolOutputSig Digit9 { get { return GetBoolOutputNamed("9"); } } + + /// + /// Gets or sets the Digit0 + /// + public BoolOutputSig Digit0 { get { return GetBoolOutputNamed("0"); } } + + /// + /// Gets or sets the Misc1 + /// + public BoolOutputSig Misc1 { get { return GetBoolOutputNamed(Misc1SigName); } } + + /// + /// Gets or sets the Misc2 + /// + public BoolOutputSig Misc2 { get { return GetBoolOutputNamed(Misc2SigName); } } + + /// + /// Constructor + /// + /// smart object + /// use user handler if true + public SmartObjectNumeric(SmartObject so, bool useUserObjectHandler) : base(so, useUserObjectHandler) + { + Misc1SigName = "Misc_1"; + Misc2SigName = "Misc_2"; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceList.cs b/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceList.cs index d7a5c839..4ed42d49 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceList.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceList.cs @@ -1,290 +1,305 @@ -using System; + +using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; +using Crestron.SimplSharpPro.UI; + using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -//***************************************************************************** -/// -/// Wrapper class for subpage reference list. Contains helpful methods to get at the various signal groupings -/// and to get individual signals using an index and a join. -/// -public class SubpageReferenceList +namespace PepperDash.Essentials.Core { + ////***************************************************************************** + ///// + ///// Base class for all subpage reference list controllers + ///// + //public class SubpageReferenceListController + //{ + // public SubpageReferenceList TheList { get; protected set; } + //} + + //***************************************************************************** /// - /// Gets or sets the Count + /// Wrapper class for subpage reference list. Contains helpful methods to get at the various signal groupings + /// and to get individual signals using an index and a join. /// - public ushort Count + public class SubpageReferenceList { - get { return SetNumberOfItemsSig.UShortValue; } - set { SetNumberOfItemsSig.UShortValue = value; } - } - - /// - /// Gets or sets the MaxDefinedItems - /// - public ushort MaxDefinedItems { get; private set; } - - /// - /// Gets or sets the ScrollToItemSig - /// - public UShortInputSig ScrollToItemSig { get; private set; } - - UShortInputSig SetNumberOfItemsSig; - - /// - /// Gets or sets the BoolIncrement - /// - public uint BoolIncrement { get; protected set; } - - /// - /// Gets or sets the UShortIncrement - /// - public uint UShortIncrement { get; protected set; } - - /// - /// Gets or sets the StringIncrement - /// - public uint StringIncrement { get; protected set; } - - /// - /// Gets or sets the SRL - /// - protected readonly SmartObject SRL; - - /// - /// Gets the list of items in the SRL - /// - protected readonly List Items = new List(); - - /// - /// Constructor - /// - /// trilist for the smart object - /// smart object ID - /// - /// - /// - public SubpageReferenceList(BasicTriListWithSmartObject triList, uint smartObjectId, - uint boolIncrement, uint ushortIncrement, uint stringIncrement) - { - SmartObject obj; - // Fail cleanly if not defined - if (triList.SmartObjects == null || triList.SmartObjects.Count == 0) + /// + /// Gets or sets the Count + /// + public ushort Count { - Debug.LogMessage(LogEventLevel.Information, "TriList {0:X2} Smart objects have not been loaded", triList.ID, smartObjectId); - return; + get { return SetNumberOfItemsSig.UShortValue; } + set { SetNumberOfItemsSig.UShortValue = value; } } - if (triList.SmartObjects.TryGetValue(smartObjectId, out obj)) + + /// + /// Gets or sets the MaxDefinedItems + /// + public ushort MaxDefinedItems { get; private set; } + + /// + /// Gets or sets the ScrollToItemSig + /// + public UShortInputSig ScrollToItemSig { get; private set; } + + UShortInputSig SetNumberOfItemsSig; + + /// + /// Gets or sets the BoolIncrement + /// + public uint BoolIncrement { get; protected set; } + + /// + /// Gets or sets the UShortIncrement + /// + public uint UShortIncrement { get; protected set; } + + /// + /// Gets or sets the StringIncrement + /// + public uint StringIncrement { get; protected set; } + + /// + /// Gets or sets the SRL + /// + protected readonly SmartObject SRL; + + /// + /// Gets the list of items in the SRL + /// + protected readonly List Items = new List(); + + /// + /// Constructor + /// + /// trilist for the smart object + /// smart object ID + /// + /// + /// + public SubpageReferenceList(BasicTriListWithSmartObject triList, uint smartObjectId, + uint boolIncrement, uint ushortIncrement, uint stringIncrement) { - SRL = triList.SmartObjects[smartObjectId]; - ScrollToItemSig = SRL.UShortInput["Scroll To Item"]; - SetNumberOfItemsSig = SRL.UShortInput["Set Number of Items"]; - BoolIncrement = boolIncrement; - UShortIncrement = ushortIncrement; - StringIncrement = stringIncrement; + SmartObject obj; + // Fail cleanly if not defined + if (triList.SmartObjects == null || triList.SmartObjects.Count == 0) + { + Debug.LogMessage(LogEventLevel.Information, "TriList {0:X2} Smart objects have not been loaded", triList.ID, smartObjectId); + return; + } + if (triList.SmartObjects.TryGetValue(smartObjectId, out obj)) + { + SRL = triList.SmartObjects[smartObjectId]; + ScrollToItemSig = SRL.UShortInput["Scroll To Item"]; + SetNumberOfItemsSig = SRL.UShortInput["Set Number of Items"]; + BoolIncrement = boolIncrement; + UShortIncrement = ushortIncrement; + StringIncrement = stringIncrement; - // Count the enable lines to see what max items is - MaxDefinedItems = (ushort)SRL.BooleanInput - .Where(s => s.Name.Contains("Enable")).Count(); - Debug.LogMessage(LogEventLevel.Verbose, "SRL {0} contains max {1} items", SRL.ID, MaxDefinedItems); + // Count the enable lines to see what max items is + MaxDefinedItems = (ushort)SRL.BooleanInput + .Where(s => s.Name.Contains("Enable")).Count(); + Debug.LogMessage(LogEventLevel.Verbose, "SRL {0} contains max {1} items", SRL.ID, MaxDefinedItems); - SRL.SigChange -= new SmartObjectSigChangeEventHandler(SRL_SigChange); - SRL.SigChange += new SmartObjectSigChangeEventHandler(SRL_SigChange); + SRL.SigChange -= new SmartObjectSigChangeEventHandler(SRL_SigChange); + SRL.SigChange += new SmartObjectSigChangeEventHandler(SRL_SigChange); + } + else + Debug.LogMessage(LogEventLevel.Information, "ERROR: TriList 0x{0:X2} Cannot load smart object {1}. Verify correct SGD file is loaded", + triList.ID, smartObjectId); } - else - Debug.LogMessage(LogEventLevel.Information, "ERROR: TriList 0x{0:X2} Cannot load smart object {1}. Verify correct SGD file is loaded", - triList.ID, smartObjectId); - } - /// - /// Adds item to saved list of displayed items (not necessarily in order) - /// DOES NOT adjust Count - /// - /// - /// - /// AddItem method - /// - public void AddItem(SubpageReferenceListItem item) - { - Items.Add(item); - } + /// + /// Adds item to saved list of displayed items (not necessarily in order) + /// DOES NOT adjust Count + /// + /// + /// + /// AddItem method + /// + public void AddItem(SubpageReferenceListItem item) + { + Items.Add(item); + } - /// - /// Clear method - /// - public void Clear() - { - // If a line item needs to disconnect an CueActionPair or do something to release RAM - foreach (var item in Items) item.Clear(); - // Empty the list - Items.Clear(); - // Clean up the SRL - Count = 1; + /// + /// Clear method + /// + public void Clear() + { + // If a line item needs to disconnect an CueActionPair or do something to release RAM + foreach (var item in Items) item.Clear(); + // Empty the list + Items.Clear(); + // Clean up the SRL + Count = 1; - ScrollToItemSig.UShortValue = 1; - } + ScrollToItemSig.UShortValue = 1; + } - /// - /// Refresh method - /// - public void Refresh() - { - foreach (var item in Items) item.Refresh(); - } + /// + /// Refresh method + /// + public void Refresh() + { + foreach (var item in Items) item.Refresh(); + } - // Helpers to get sigs by their weird SO names + // Helpers to get sigs by their weird SO names - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line or item position on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public BoolOutputSig GetBoolFeedbackSig(uint index, uint sigNum) - { - if (sigNum > BoolIncrement) return null; - return SRL.BooleanOutput.FirstOrDefault(s => s.Name.Equals(GetBoolFeedbackSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line or item position on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public BoolOutputSig GetBoolFeedbackSig(uint index, uint sigNum) + { + if (sigNum > BoolIncrement) return null; + return SRL.BooleanOutput.FirstOrDefault(s => s.Name.Equals(GetBoolFeedbackSigName(index, sigNum))); + } - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line or item position on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public UShortOutputSig GetUShortOutputSig(uint index, uint sigNum) - { - if (sigNum > UShortIncrement) return null; - return SRL.UShortOutput.FirstOrDefault(s => s.Name.Equals(GetUShortOutputSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line or item position on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public UShortOutputSig GetUShortOutputSig(uint index, uint sigNum) + { + if (sigNum > UShortIncrement) return null; + return SRL.UShortOutput.FirstOrDefault(s => s.Name.Equals(GetUShortOutputSigName(index, sigNum))); + } - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line or item position on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public StringOutputSig GetStringOutputSig(uint index, uint sigNum) - { - if (sigNum > StringIncrement) return null; - return SRL.StringOutput.FirstOrDefault(s => s.Name.Equals(GetStringOutputSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line or item position on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public StringOutputSig GetStringOutputSig(uint index, uint sigNum) + { + if (sigNum > StringIncrement) return null; + return SRL.StringOutput.FirstOrDefault(s => s.Name.Equals(GetStringOutputSigName(index, sigNum))); + } - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public BoolInputSig BoolInputSig(uint index, uint sigNum) - { - if (sigNum > BoolIncrement) return null; - return SRL.BooleanInput.FirstOrDefault(s => s.Name.Equals(GetBoolInputSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public BoolInputSig BoolInputSig(uint index, uint sigNum) + { + if (sigNum > BoolIncrement) return null; + return SRL.BooleanInput.FirstOrDefault(s => s.Name.Equals(GetBoolInputSigName(index, sigNum))); + } - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public UShortInputSig UShortInputSig(uint index, uint sigNum) - { - if (sigNum > UShortIncrement) return null; - return SRL.UShortInput.FirstOrDefault(s => s.Name.Equals(GetUShortInputSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public UShortInputSig UShortInputSig(uint index, uint sigNum) + { + if (sigNum > UShortIncrement) return null; + return SRL.UShortInput.FirstOrDefault(s => s.Name.Equals(GetUShortInputSigName(index, sigNum))); + } - /// - /// Returns the Sig associated with a given SRL line index - /// and the join number of the object on the SRL subpage. - /// Note: If the join number exceeds the increment range, or the count of Sigs on the - /// list object, this will return null - /// - /// The line on the SRL - /// The join number of the item on the SRL subpage - /// A Sig or null if the numbers are out of range - public StringInputSig StringInputSig(uint index, uint sigNum) - { - if (sigNum > StringIncrement) return null; - return SRL.StringInput.FirstOrDefault(s => s.Name.Equals(GetStringInputSigName(index, sigNum))); - } + /// + /// Returns the Sig associated with a given SRL line index + /// and the join number of the object on the SRL subpage. + /// Note: If the join number exceeds the increment range, or the count of Sigs on the + /// list object, this will return null + /// + /// The line on the SRL + /// The join number of the item on the SRL subpage + /// A Sig or null if the numbers are out of range + public StringInputSig StringInputSig(uint index, uint sigNum) + { + if (sigNum > StringIncrement) return null; + return SRL.StringInput.FirstOrDefault(s => s.Name.Equals(GetStringInputSigName(index, sigNum))); + } - // Helpers to get signal names + // Helpers to get signal names - string GetBoolFeedbackSigName(uint index, uint sigNum) - { - var num = (index - 1) * BoolIncrement + sigNum; - return String.Format("press{0}", num); - } + string GetBoolFeedbackSigName(uint index, uint sigNum) + { + var num = (index - 1) * BoolIncrement + sigNum; + return String.Format("press{0}", num); + } - string GetUShortOutputSigName(uint index, uint sigNum) - { - var num = (index - 1) * UShortIncrement + sigNum; - return String.Format("an_act{0}", num); - } + string GetUShortOutputSigName(uint index, uint sigNum) + { + var num = (index - 1) * UShortIncrement + sigNum; + return String.Format("an_act{0}", num); + } - string GetStringOutputSigName(uint index, uint sigNum) - { - var num = (index - 1) * StringIncrement + sigNum; - return String.Format("text-i{0}", num); - } + string GetStringOutputSigName(uint index, uint sigNum) + { + var num = (index - 1) * StringIncrement + sigNum; + return String.Format("text-i{0}", num); + } - string GetBoolInputSigName(uint index, uint sigNum) - { - var num = (index - 1) * BoolIncrement + sigNum; - return String.Format("fb{0}", num); - } + string GetBoolInputSigName(uint index, uint sigNum) + { + var num = (index - 1) * BoolIncrement + sigNum; + return String.Format("fb{0}", num); + } - string GetUShortInputSigName(uint index, uint sigNum) - { - var num = (index - 1) * UShortIncrement + sigNum; - return String.Format("an_fb{0}", num); - } + string GetUShortInputSigName(uint index, uint sigNum) + { + var num = (index - 1) * UShortIncrement + sigNum; + return String.Format("an_fb{0}", num); + } - string GetStringInputSigName(uint index, uint sigNum) - { - var num = (index - 1) * StringIncrement + sigNum; - return String.Format("text-o{0}", num); - } + string GetStringInputSigName(uint index, uint sigNum) + { + var num = (index - 1) * StringIncrement + sigNum; + return String.Format("text-o{0}", num); + } - /// - /// Stock SigChange handler - /// - /// - /// - /// - /// SRL_SigChange method - /// - public static void SRL_SigChange(GenericBase currentDevice, SmartObjectEventArgs args) - { - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); + /// + /// Stock SigChange handler + /// + /// + /// + /// + /// SRL_SigChange method + /// + public static void SRL_SigChange(GenericBase currentDevice, SmartObjectEventArgs args) + { + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceListItem.cs b/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceListItem.cs index 878db77c..e29d0894 100644 --- a/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceListItem.cs +++ b/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferencList/SubpageReferenceListItem.cs @@ -1,41 +1,50 @@ -namespace PepperDash.Essentials.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.UI; -/// -/// Represents a SubpageReferenceListItem -/// -public class SubpageReferenceListItem +namespace PepperDash.Essentials.Core { - /// - /// The list that this lives in - /// - protected SubpageReferenceList Owner; - - /// - /// The index of this item - /// - protected uint Index; - - /// - /// Constructor - /// - /// index of the item - /// owner of the item - public SubpageReferenceListItem(uint index, SubpageReferenceList owner) + /// + /// Represents a SubpageReferenceListItem + /// + public class SubpageReferenceListItem { - Index = index; - Owner = owner; - } + /// + /// The list that this lives in + /// + protected SubpageReferenceList Owner; - /// - /// Clear method - /// - /// - public virtual void Clear() - { - } + /// + /// The index of this item + /// + protected uint Index; - /// - /// Refresh method - /// - public virtual void Refresh() { } + /// + /// Constructor + /// + /// index of the item + /// owner of the item + public SubpageReferenceListItem(uint index, SubpageReferenceList owner) + { + Index = index; + Owner = owner; + } + + /// + /// Clear method + /// + /// + public virtual void Clear() + { + } + + /// + /// Refresh method + /// + public virtual void Refresh() { } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Timers/CountdownTimer.cs b/src/PepperDash.Essentials.Core/Timers/CountdownTimer.cs index 9a6d8c23..24ff6a1a 100644 --- a/src/PepperDash.Essentials.Core/Timers/CountdownTimer.cs +++ b/src/PepperDash.Essentials.Core/Timers/CountdownTimer.cs @@ -1,194 +1,198 @@ using System; -using System.Timers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - -/// -/// A class that represents a countdown timer with feedbacks for time remaining, percent, and seconds -/// -public class SecondsCountdownTimer: IKeyed +namespace PepperDash.Essentials.Core { /// - /// Event triggered when the timer starts. + /// Represents a SecondsCountdownTimer /// - public event EventHandler HasStarted; - /// - /// Event triggered when the timer finishes. - /// - public event EventHandler HasFinished; - /// - /// Event triggered when the timer is cancelled. - /// - public event EventHandler WasCancelled; - - /// - public string Key { get; private set; } - - /// - /// Indicates whether the timer is currently running - /// - public BoolFeedback IsRunningFeedback { get; private set; } - bool _isRunning; - - /// - /// Feedback for the percentage of time remaining - /// - public IntFeedback PercentFeedback { get; private set; } - - /// - /// Feedback for the time remaining in a string format - // - public StringFeedback TimeRemainingFeedback { get; private set; } - - /// - /// Feedback for the time remaining in seconds - /// - public IntFeedback SecondsRemainingFeedback { get; private set; } - - /// - /// When true, the timer will count down immediately upon calling Start. When false, the timer will count up, and when Finish is called, it will stop counting and fire the HasFinished event. - /// - public bool CountsDown { get; set; } - - /// - /// The number of seconds to countdown - /// - public int SecondsToCount { get; set; } - - /// - /// The time at which the timer was started. Used to calculate percent and time remaining. Will be DateTime.MinValue if the timer is not currently running. - /// - public DateTime StartTime { get; private set; } - - /// - /// The time at which the timer will finish counting down. Used to calculate percent and time remaining. Will be DateTime.MinValue if the timer is not currently running. - /// - public DateTime FinishTime { get; private set; } - - private Timer _secondTimer; - - /// - /// Constructor - /// - /// - public SecondsCountdownTimer(string key) + public class SecondsCountdownTimer: IKeyed { - Key = key; - IsRunningFeedback = new BoolFeedback(() => _isRunning); + /// + /// Event fired when the timer starts + /// + public event EventHandler HasStarted; - TimeRemainingFeedback = new StringFeedback(() => - { - // Need to handle up and down here. + /// + /// Event fired when the timer finishes + /// + public event EventHandler HasFinished; - var timeSpan = FinishTime - DateTime.Now; + /// + /// Event fired when the timer is cancelled + /// + public event EventHandler WasCancelled; - Debug.LogMessage(LogEventLevel.Verbose, - "timeSpan.Minutes == {0}, timeSpan.Seconds == {1}, timeSpan.TotalSeconds == {2}", this, - timeSpan.Minutes, timeSpan.Seconds, timeSpan.TotalSeconds); + /// + /// Gets or sets the Key + /// + public string Key { get; private set; } - if (Math.Floor(timeSpan.TotalSeconds) < 60 && Math.Floor(timeSpan.TotalSeconds) >= 0) //ignore milliseconds - { - return String.Format("{0:00}", timeSpan.Seconds); - } + /// + /// Gets or sets the IsRunningFeedback + /// + public BoolFeedback IsRunningFeedback { get; private set; } + bool _isRunning; - return Math.Floor(timeSpan.TotalSeconds) < 0 - ? "00" - : String.Format("{0:00}:{1:00}", timeSpan.Minutes, timeSpan.Seconds); - }); - - SecondsRemainingFeedback = new IntFeedback(() => (int)(FinishTime - DateTime.Now).TotalSeconds); - - PercentFeedback = - new IntFeedback( - () => - (int) - (Math.Floor((FinishTime - DateTime.Now).TotalSeconds)/ - Math.Floor((FinishTime - StartTime).TotalSeconds)*100)); - } - - /// - /// Starts the Timer - /// - public void Start() - { - if (_isRunning) - return; - StartTime = DateTime.Now; - FinishTime = StartTime + TimeSpan.FromSeconds(SecondsToCount); - - if (_secondTimer != null) - _secondTimer.Stop(); - _secondTimer = new Timer(1000) { AutoReset = true }; - _secondTimer.Elapsed += (s, e) => SecondElapsedTimerCallback(null); - _secondTimer.Start(); - SecondElapsedTimerCallback(null); - _isRunning = true; - IsRunningFeedback.FireUpdate(); - - var handler = HasStarted; - if (handler != null) - handler(this, new EventArgs()); - } - - /// - /// Restarts the timer - /// - public void Reset() - { - _isRunning = false; - IsRunningFeedback.FireUpdate(); - Start(); - } - - /// - /// Cancels the timer (without triggering it to finish) - /// - public void Cancel() - { - StopHelper(); + /// + /// Gets or sets the PercentFeedback + /// + public IntFeedback PercentFeedback { get; private set; } - var handler = WasCancelled; - if (handler != null) - handler(this, new EventArgs()); - } + /// + /// Gets or sets the TimeRemainingFeedback + /// + public StringFeedback TimeRemainingFeedback { get; private set; } - /// - /// Called upon expiration, or calling this will force timer to finish. - /// - public void Finish() - { - StopHelper(); + /// + /// Gets or sets the SecondsRemainingFeedback + /// + public IntFeedback SecondsRemainingFeedback { get; private set; } - var handler = HasFinished; - if (handler != null) - handler(this, new EventArgs()); - } + /// + /// Gets or sets the CountsDown + /// + public bool CountsDown { get; set; } - void StopHelper() - { - if (_secondTimer != null) + /// + /// Gets or sets the SecondsToCount + /// + public int SecondsToCount { get; set; } + + /// + /// Gets or sets the StartTime + /// + public DateTime StartTime { get; private set; } + /// + /// Gets or sets the FinishTime + /// + public DateTime FinishTime { get; private set; } + + private CTimer _secondTimer; + + /// + /// Constructor + /// + /// + public SecondsCountdownTimer(string key) { - _secondTimer.Stop(); - _secondTimer = null; + Key = key; + IsRunningFeedback = new BoolFeedback(() => _isRunning); + + TimeRemainingFeedback = new StringFeedback(() => + { + // Need to handle up and down here. + + var timeSpan = FinishTime - DateTime.Now; + + Debug.LogMessage(LogEventLevel.Verbose, + "timeSpan.Minutes == {0}, timeSpan.Seconds == {1}, timeSpan.TotalSeconds == {2}", this, + timeSpan.Minutes, timeSpan.Seconds, timeSpan.TotalSeconds); + + if (Math.Floor(timeSpan.TotalSeconds) < 60 && Math.Floor(timeSpan.TotalSeconds) >= 0) //ignore milliseconds + { + return String.Format("{0:00}", timeSpan.Seconds); + } + + return Math.Floor(timeSpan.TotalSeconds) < 0 + ? "00" + : String.Format("{0:00}:{1:00}", timeSpan.Minutes, timeSpan.Seconds); + }); + + SecondsRemainingFeedback = new IntFeedback(() => (int)(FinishTime - DateTime.Now).TotalSeconds); + + PercentFeedback = + new IntFeedback( + () => + (int) + (Math.Floor((FinishTime - DateTime.Now).TotalSeconds)/ + Math.Floor((FinishTime - StartTime).TotalSeconds)*100)); } - _isRunning = false; - IsRunningFeedback.FireUpdate(); - } - - void SecondElapsedTimerCallback(object o) - { - if (DateTime.Now >= FinishTime) + /// + /// Start method + /// + public void Start() { - Finish(); - return; + if (_isRunning) + return; + StartTime = DateTime.Now; + FinishTime = StartTime + TimeSpan.FromSeconds(SecondsToCount); + + if (_secondTimer != null) + _secondTimer.Stop(); + _secondTimer = new CTimer(SecondElapsedTimerCallback, null, 0, 1000); + _isRunning = true; + IsRunningFeedback.FireUpdate(); + + var handler = HasStarted; + if (handler != null) + handler(this, new EventArgs()); } - PercentFeedback.FireUpdate(); - TimeRemainingFeedback.FireUpdate(); - SecondsRemainingFeedback.FireUpdate(); + /// + /// Reset method + /// + public void Reset() + { + _isRunning = false; + IsRunningFeedback.FireUpdate(); + Start(); + } + + /// + /// Cancel method + /// + public void Cancel() + { + StopHelper(); + + var handler = WasCancelled; + if (handler != null) + handler(this, new EventArgs()); + } + + /// + /// Finish method + /// + public void Finish() + { + StopHelper(); + + var handler = HasFinished; + if (handler != null) + handler(this, new EventArgs()); + } + + void StopHelper() + { + if (_secondTimer != null) + { + _secondTimer.Stop(); + _secondTimer = null; + } + + _isRunning = false; + IsRunningFeedback.FireUpdate(); + } + + void SecondElapsedTimerCallback(object o) + { + if (DateTime.Now >= FinishTime) + { + Finish(); + return; + } + + PercentFeedback.FireUpdate(); + TimeRemainingFeedback.FireUpdate(); + SecondsRemainingFeedback.FireUpdate(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Timers/RetriggerableTimer.cs b/src/PepperDash.Essentials.Core/Timers/RetriggerableTimer.cs index 26fd3d72..f06ba3cd 100644 --- a/src/PepperDash.Essentials.Core/Timers/RetriggerableTimer.cs +++ b/src/PepperDash.Essentials.Core/Timers/RetriggerableTimer.cs @@ -1,214 +1,222 @@ -using System; + + +using System; using System.Collections.Generic; -using System.Timers; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + using PepperDash.Core; +using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; + using Newtonsoft.Json; using Serilog.Events; -namespace PepperDash.Essentials.Core.Timers; - -/// -/// A device that runs a retriggerable timer and can execute actions specified in config -/// -[Description("A retriggerable timer device")] -public class RetriggerableTimer : EssentialsDevice +namespace PepperDash.Essentials.Core.Timers { - private RetriggerableTimerPropertiesConfig _propertiesConfig; - - private Timer _timer; - private long _timerIntervalMs; - /// - /// Constructor for RetriggerableTimer + /// A device that runs a retriggerable timer and can execute actions specified in config /// - /// - /// - public RetriggerableTimer(string key, DeviceConfig config) - : base(key, config.Name) + [Description("A retriggerable timer device")] + public class RetriggerableTimer : EssentialsDevice { - var props = config.Properties.ToObject(); - _propertiesConfig = props; + private RetriggerableTimerPropertiesConfig _propertiesConfig; - if (_propertiesConfig != null) - { - _timerIntervalMs = _propertiesConfig.TimerIntervalMs; - } - } + private CTimer _timer; + private long _timerIntervalMs; - /// - protected override bool CustomActivate() - { - if (_propertiesConfig.StartTimerOnActivation) + /// + /// Constructor + /// + /// key of the timer + /// configuration for the timer + public RetriggerableTimer(string key, DeviceConfig config) + : base(key, config.Name) { - StartTimer(); + var props = config.Properties.ToObject(); + _propertiesConfig = props; + + if (_propertiesConfig != null) + { + _timerIntervalMs = _propertiesConfig.TimerIntervalMs; + } } - return base.CustomActivate(); - } - - private void CleanUpTimer() - { - if (_timer != null) + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() { + if (_propertiesConfig.StartTimerOnActivation) + { + StartTimer(); + } + + return base.CustomActivate(); + } + + private void CleanUpTimer() + { + if (_timer != null) + { + _timer.Stop(); + _timer.Dispose(); + } + + _timer = null; + } + + /// + /// StartTimer method + /// + public void StartTimer() + { + CleanUpTimer(); + Debug.LogMessage(LogEventLevel.Information, this, "Starting Timer"); + + _timer = new CTimer(TimerElapsedCallback, GetActionFromConfig(eRetriggerableTimerEvents.Elapsed), _timerIntervalMs, _timerIntervalMs); + } + + /// + /// StopTimer method + /// + public void StopTimer() + { + Debug.LogMessage(LogEventLevel.Information, this, "Stopping Timer"); _timer.Stop(); - _timer.Dispose(); + + ExecuteAction(GetActionFromConfig(eRetriggerableTimerEvents.Stopped)); } - _timer = null; - } - - /// - /// Starts the timer with the interval specified in config. When the timer elapses, it executes the action specified in config for the Elapsed event. If the timer is already running, it will reset and start again. - /// When the timer is stopped, it executes the action specified in config for the Stopped event. - /// - public void StartTimer() - { - CleanUpTimer(); - Debug.LogMessage(LogEventLevel.Information, this, "Starting Timer"); - - var action = GetActionFromConfig(eRetriggerableTimerEvents.Elapsed); - _timer = new Timer(_timerIntervalMs) { AutoReset = true }; - _timer.Elapsed += (s, e) => TimerElapsedCallback(action); - _timer.Start(); - } - - /// - /// Stops the timer. If the timer is stopped before it elapses, it will execute the action specified in config for the Stopped event. If the timer is not running, this method does nothing. - /// If the timer is running, it will stop the timer and execute the Stopped action from config. If the timer is not running, it will do nothing. - /// If the timer is running and the Stopped action is not specified in config, it will stop the timer and do nothing else. If the timer is running and the Stopped action is specified in config, it will stop the timer and execute the action. If the timer is not running, it will do nothing regardless - /// - public void StopTimer() - { - Debug.LogMessage(LogEventLevel.Information, this, "Stopping Timer"); - _timer.Stop(); - - ExecuteAction(GetActionFromConfig(eRetriggerableTimerEvents.Stopped)); - } - - private DeviceActionWrapper GetActionFromConfig(eRetriggerableTimerEvents eventType) - { - var action = _propertiesConfig.Events[eRetriggerableTimerEvents.Elapsed]; - - if (action != null) - return action; - else return null; - } - - /// - /// Executes the Elapsed action from confing when the timer elapses - /// - /// The action to execute when the timer elapses - private void TimerElapsedCallback(object action) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Timer Elapsed. Executing Action"); - - if (action == null) + private DeviceActionWrapper GetActionFromConfig(eRetriggerableTimerEvents eventType) { - Debug.LogMessage(LogEventLevel.Debug, this, "Timer elapsed but unable to execute action. Action is null."); - return; + var action = _propertiesConfig.Events[eRetriggerableTimerEvents.Elapsed]; + + if (action != null) + return action; + else return null; } - var devAction = action as DeviceActionWrapper; - if (devAction != null) - ExecuteAction(devAction); - else + /// + /// Executes the Elapsed action from confing when the timer elapses + /// + /// action to be executed + private void TimerElapsedCallback(object action) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to cast action as DeviceActionWrapper. Cannot Execute"); + Debug.LogMessage(LogEventLevel.Debug, this, "Timer Elapsed. Executing Action"); + + if (action == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Timer elapsed but unable to execute action. Action is null."); + return; + } + + var devAction = action as DeviceActionWrapper; + if (devAction != null) + ExecuteAction(devAction); + else + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Unable to cast action as DeviceActionWrapper. Cannot Execute"); + } + } - } - - private void ExecuteAction(DeviceActionWrapper action) - { - if (action == null) - return; - - try + private void ExecuteAction(DeviceActionWrapper action) { - DeviceJsonApi.DoDeviceAction(action); + if (action == null) + return; + + try + { + DeviceJsonApi.DoDeviceAction(action); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Error Executing Action: {0}", e); + } + //finally // Not sure this is needed + //{ + // _Timer.Reset(0, _TimerIntervalMs); + //} } - catch (Exception e) + } + + /// + /// Configuration Properties for RetriggerableTimer + /// + public class RetriggerableTimerPropertiesConfig + { + /// + /// Start the timer on device activation + /// + [JsonProperty("startTimerOnActivation")] + public bool StartTimerOnActivation { get; set; } + + /// + /// Timer interval in milliseconds + /// + [JsonProperty("timerIntervalMs")] + public long TimerIntervalMs { get; set; } + + /// + /// Events and their associated actions + /// + [JsonProperty("events")] + public Dictionary Events { get; set; } + + /// + /// Constructor + /// + public RetriggerableTimerPropertiesConfig() { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error Executing Action: {0}", e); + Events = new Dictionary(); } - //finally // Not sure this is needed - //{ - // _Timer.Reset(0, _TimerIntervalMs); - //} } -} - -/// -/// Configuration Properties for RetriggerableTimer -/// -public class RetriggerableTimerPropertiesConfig -{ - /// - /// When true, the timer will start immediately upon activation. When false, the timer will not start until StartTimer is called. - /// - [JsonProperty("startTimerOnActivation")] - public bool StartTimerOnActivation { get; set; } /// - /// The interval at which the timer elapses, in milliseconds. This is required and must be greater than 0. If this value is not set or is less than or equal to 0, the timer will not start and an error will be logged. + /// Enumeration of eRetriggerableTimerEvents values /// - [JsonProperty("timerIntervalMs")] - public long TimerIntervalMs { get; set; } - - /// - /// The actions to execute when timer events occur. The key is the type of event, and the value is the action to execute when that event occurs. - /// This is required and must contain at least an action for the Elapsed event. - /// If an action for the Stopped event is not included, then when the timer is stopped, it will simply stop without executing any action. - /// - [JsonProperty("events")] - public Dictionary Events { get; set; } - - /// - /// Constructor - /// - public RetriggerableTimerPropertiesConfig() + public enum eRetriggerableTimerEvents { - Events = new Dictionary(); + /// + /// Elapsed event + /// + Elapsed, + + /// + /// Stopped event + /// + Stopped, } -} - -/// -/// The set of values describing events on the timer -/// -public enum eRetriggerableTimerEvents -{ - /// - /// Elapsed event state - /// - Elapsed, /// - /// Stopped event state + /// Factory class /// - Stopped, -} - -/// -/// Factory class -/// -public class RetriggerableTimerFactory : EssentialsDeviceFactory -{ - /// - /// Constructor for factory - /// - /// - public RetriggerableTimerFactory() + public class RetriggerableTimerFactory : EssentialsDeviceFactory { - TypeNames = new List() { "retriggerabletimer" }; + /// + /// Constructor + /// + public RetriggerableTimerFactory() + { + TypeNames = new List() { "retriggerabletimer" }; + } + + /// + /// BuildDevice method + /// + /// device config + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RetriggerableTimer Device"); + + return new RetriggerableTimer(dc.Key, dc); + } } - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RetriggerableTimer Device"); - return new RetriggerableTimer(dc.Key, dc); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs index 478869b3..2cc9ab9a 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/CrestronTouchpanelPropertiesConfig.cs @@ -1,90 +1,149 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials.Core; - -public class CrestronTouchpanelPropertiesConfig +namespace PepperDash.Essentials.Core { - [JsonProperty("control")] - public EssentialsControlPropertiesConfig ControlProperties { get; set; } - - [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] - public string IpId { get; set; } - - [JsonProperty("defaultRoomKey", NullValueHandling = NullValueHandling.Ignore)] - public string DefaultRoomKey { get; set; } - - [JsonProperty("roomListKey", NullValueHandling = NullValueHandling.Ignore)] - public string RoomListKey { get; set; } - - [JsonProperty("sgdFile", NullValueHandling = NullValueHandling.Ignore)] - public string SgdFile { get; set; } - - [JsonProperty("projectName", NullValueHandling = NullValueHandling.Ignore)] - public string ProjectName { get; set; } - - [JsonProperty("showVolumeGauge", NullValueHandling = NullValueHandling.Ignore)] - public bool? ShowVolumeGauge { get; set; } - - [JsonProperty("usesSplashPage", NullValueHandling = NullValueHandling.Ignore)] - public bool? UsesSplashPage { get; set; } - - [JsonProperty("showDate", NullValueHandling = NullValueHandling.Ignore)] - public bool? ShowDate { get; set; } - - [JsonProperty("showTime", NullValueHandling = NullValueHandling.Ignore)] - public bool? ShowTime { get; set; } - - [JsonProperty("setup", NullValueHandling = NullValueHandling.Ignore)] - public UiSetupPropertiesConfig Setup { get; set; } - - [JsonProperty("headerStyle", NullValueHandling = NullValueHandling.Ignore)] - public string HeaderStyle { get; set; } - - [JsonProperty("includeInFusionRoomHealth", NullValueHandling = NullValueHandling.Ignore)] - public bool? IncludeInFusionRoomHealth { get; set; } - - [JsonProperty("screenSaverTimeoutMin", NullValueHandling = NullValueHandling.Ignore)] - public uint? ScreenSaverTimeoutMin { get; set; } - - [JsonProperty("screenSaverMovePositionIntervalMs", NullValueHandling = NullValueHandling.Ignore)] - public uint? ScreenSaverMovePositionIntervalMs { get; set; } - - /// - /// The count of sources that will trigger the "additional" arrows to show on the SRL. - /// Defaults to 5 + /// Represents a CrestronTouchpanelPropertiesConfig /// - [JsonProperty("sourcesOverflowCount", NullValueHandling = NullValueHandling.Ignore)] - public int? SourcesOverflowCount { get; set; } - - public CrestronTouchpanelPropertiesConfig() : this(false) { } - - public CrestronTouchpanelPropertiesConfig(bool setDefaultValues = false) + public class CrestronTouchpanelPropertiesConfig { - if(!setDefaultValues) { return; } - SourcesOverflowCount = 5; - HeaderStyle = Habanero; + /// + /// Gets or sets the ControlProperties + /// + [JsonProperty("control")] + public EssentialsControlPropertiesConfig ControlProperties { get; set; } - // Default values - ScreenSaverTimeoutMin = 5; - ScreenSaverMovePositionIntervalMs = 15000; - } + /// + /// Gets or sets the IpId + /// + [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] + public string IpId { get; set; } + + /// + /// Gets or sets the DefaultRoomKey + /// + [JsonProperty("defaultRoomKey", NullValueHandling = NullValueHandling.Ignore)] + public string DefaultRoomKey { get; set; } + + /// + /// Gets or sets the RoomListKey + /// + [JsonProperty("roomListKey", NullValueHandling = NullValueHandling.Ignore)] + public string RoomListKey { get; set; } + + /// + /// Gets or sets the SgdFile + /// + [JsonProperty("sgdFile", NullValueHandling = NullValueHandling.Ignore)] + public string SgdFile { get; set; } + + /// + /// Gets or sets the ProjectName + /// + [JsonProperty("projectName", NullValueHandling = NullValueHandling.Ignore)] + public string ProjectName { get; set; } + + /// + /// Gets or sets the ShowVolumeGauge + /// + [JsonProperty("showVolumeGauge", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowVolumeGauge { get; set; } + + /// + /// Gets or sets the UsesSplashPage + /// + [JsonProperty("usesSplashPage", NullValueHandling = NullValueHandling.Ignore)] + public bool? UsesSplashPage { get; set; } + + /// + /// Gets or sets the ShowDate + /// + [JsonProperty("showDate", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowDate { get; set; } + + /// + /// Gets or sets the ShowTime + /// + [JsonProperty("showTime", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowTime { get; set; } + + /// + /// Gets or sets the Setup + /// + [JsonProperty("setup", NullValueHandling = NullValueHandling.Ignore)] + public UiSetupPropertiesConfig Setup { get; set; } + + /// + /// Gets or sets the HeaderStyle + /// + [JsonProperty("headerStyle", NullValueHandling = NullValueHandling.Ignore)] + public string HeaderStyle { get; set; } + + /// + /// Gets or sets the IncludeInFusionRoomHealth + /// + [JsonProperty("includeInFusionRoomHealth", NullValueHandling = NullValueHandling.Ignore)] + public bool? IncludeInFusionRoomHealth { get; set; } + + /// + /// Gets or sets the ScreenSaverTimeoutMin + /// + [JsonProperty("screenSaverTimeoutMin", NullValueHandling = NullValueHandling.Ignore)] + public uint? ScreenSaverTimeoutMin { get; set; } + + /// + /// Gets or sets the ScreenSaverMovePositionIntervalMs + /// + [JsonProperty("screenSaverMovePositionIntervalMs", NullValueHandling = NullValueHandling.Ignore)] + public uint? ScreenSaverMovePositionIntervalMs { get; set; } + + + /// + /// The count of sources that will trigger the "additional" arrows to show on the SRL. + /// Defaults to 5 + /// + [JsonProperty("sourcesOverflowCount", NullValueHandling = NullValueHandling.Ignore)] + public int? SourcesOverflowCount { get; set; } + + /// + /// Constructor + /// + public CrestronTouchpanelPropertiesConfig() : this(false) { } + + /// + /// Constructor + /// + /// set values to default if true + public CrestronTouchpanelPropertiesConfig(bool setDefaultValues = false) + { + if(!setDefaultValues) { return; } + SourcesOverflowCount = 5; + HeaderStyle = Habanero; + + // Default values + ScreenSaverTimeoutMin = 5; + ScreenSaverMovePositionIntervalMs = 15000; + } + + /// + /// "habanero" + /// + public const string Habanero = "habanero"; + /// + /// "verbose" + /// + public const string Verbose = "verbose"; + } /// - /// "habanero" + /// Represents a UiSetupPropertiesConfig /// - public const string Habanero = "habanero"; - /// - /// "verbose" - /// - public const string Verbose = "verbose"; -} - -/// -/// -/// -public class UiSetupPropertiesConfig -{ - [JsonProperty("isVisible", NullValueHandling = NullValueHandling.Ignore)] - public bool IsVisible { get; set; } + public class UiSetupPropertiesConfig + { + /// + /// Gets or sets the IsVisible + /// + [JsonProperty("isVisible", NullValueHandling = NullValueHandling.Ignore)] + public bool IsVisible { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs b/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs index 0d18f133..0e74ed93 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs @@ -1,15 +1,20 @@ -using Crestron.SimplSharpPro.DeviceSupport; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core; - - -/// -/// Defines the contract for IHasBasicTriListWithSmartObject -/// -public interface IHasBasicTriListWithSmartObject +namespace PepperDash.Essentials.Core { /// - /// Gets the Panel + /// Defines the contract for IHasBasicTriListWithSmartObject /// - BasicTriListWithSmartObject Panel { get; } -} + public interface IHasBasicTriListWithSmartObject + { + /// + /// Gets the Panel + /// + BasicTriListWithSmartObject Panel { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Keyboards/HabaneroKeyboardController.cs b/src/PepperDash.Essentials.Core/Touchpanels/Keyboards/HabaneroKeyboardController.cs index 5c70b740..89346ae7 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Keyboards/HabaneroKeyboardController.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Keyboards/HabaneroKeyboardController.cs @@ -1,504 +1,513 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; -namespace PepperDash.Essentials.Core.Touchpanels.Keyboards; - -/// -/// Controller for the Habanero keyboard. This class handles all interaction with the keyboard, including showing/hiding, shift states, and key presses. It exposes a KeyPress event for single key presses, and an OutputFeedback string that contains the full text of what's been entered on the keyboard. -/// -public class HabaneroKeyboardController +namespace PepperDash.Essentials.Core.Touchpanels.Keyboards { /// - /// Single-key press events, rather than using a built-up text string on the OutputFeedback + /// Represents a HabaneroKeyboardController /// - public event EventHandler KeyPress; - - /// - /// The BasicTriList that the keyboard is connected to. This is used for all interaction with the keyboard, including showing/hiding, setting button text/visibility, and handling button presses. - /// - public BasicTriList TriList { get; private set; } - - /// - /// Feedback for the current text entered on the keyboard. This is updated whenever a key is pressed, and can be used to get the full string of what's been entered on the keyboard. - /// - public StringFeedback OutputFeedback { get; private set; } - - /// - /// Indicates whether the keyboard is currently visible. This is updated when Show and Hide are called, and can be used to determine the current visibility state of the keyboard. - /// - public bool IsVisible { get; private set; } - - /// - /// The string displayed on the ".com" button. - /// - public string DotComButtonString { get; set; } - - /// - /// The text displayed on the "Go" button. - /// - public string GoButtonText { get; set; } - - /// - /// The text displayed on the secondary button. - /// - public string SecondaryButtonText { get; set; } - - /// - /// Indicates whether the "Go" button is visible. - /// - public bool GoButtonVisible { get; set; } - - /// - /// Indicates whether the secondary button is visible. - /// - public bool SecondaryButtonVisible { get; set; } - - int ShiftMode = 0; - - StringBuilder Output; - - /// - /// An action that is run when the keyboard is hidden, either by calling the Hide method or by pressing the close button on the keyboard. This can be used to perform any necessary cleanup or state updates when the keyboard is dismissed. - /// - /// - public Action HideAction { get; set; } - - Timer BackspaceTimer; - - /// - /// - /// - /// - public HabaneroKeyboardController(BasicTriList trilist) + public class HabaneroKeyboardController { - TriList = trilist; - Output = new StringBuilder(); - OutputFeedback = new StringFeedback(() => Output.ToString()); - DotComButtonString = ".com"; - } + /// + /// Single-key press events, rather than using a built-up text string on the OutputFeedback + /// + public event EventHandler KeyPress; - /// - /// Shows the keyboard and attaches sig handlers in the range of 2901-2969 - /// - public void Show() - { - if (IsVisible) - return; + /// + /// Gets or sets the TriList + /// + public BasicTriList TriList { get; private set; } - TriList.SetSigTrueAction(ClosePressJoin, Hide); - TriList.SetSigTrueAction(GoButtonPressJoin, () => OnKeyPress(KeyboardSpecialKey.GoButton)); - TriList.SetSigTrueAction(SecondaryButtonPressJoin, () => OnKeyPress(KeyboardSpecialKey.SecondaryButton)); - TriList.SetSigTrueAction(2921, () => Press(A(ShiftMode))); - TriList.SetSigTrueAction(2922, () => Press(B(ShiftMode))); - TriList.SetSigTrueAction(2923, () => Press(C(ShiftMode))); - TriList.SetSigTrueAction(2924, () => Press(D(ShiftMode))); - TriList.SetSigTrueAction(2925, () => Press(E(ShiftMode))); - TriList.SetSigTrueAction(2926, () => Press(F(ShiftMode))); - TriList.SetSigTrueAction(2927, () => Press(G(ShiftMode))); - TriList.SetSigTrueAction(2928, () => Press(H(ShiftMode))); - TriList.SetSigTrueAction(2929, () => Press(I(ShiftMode))); - TriList.SetSigTrueAction(2930, () => Press(J(ShiftMode))); - TriList.SetSigTrueAction(2931, () => Press(K(ShiftMode))); - TriList.SetSigTrueAction(2932, () => Press(L(ShiftMode))); - TriList.SetSigTrueAction(2933, () => Press(M(ShiftMode))); - TriList.SetSigTrueAction(2934, () => Press(N(ShiftMode))); - TriList.SetSigTrueAction(2935, () => Press(O(ShiftMode))); - TriList.SetSigTrueAction(2936, () => Press(P(ShiftMode))); - TriList.SetSigTrueAction(2937, () => Press(Q(ShiftMode))); - TriList.SetSigTrueAction(2938, () => Press(R(ShiftMode))); - TriList.SetSigTrueAction(2939, () => Press(S(ShiftMode))); - TriList.SetSigTrueAction(2940, () => Press(T(ShiftMode))); - TriList.SetSigTrueAction(2941, () => Press(U(ShiftMode))); - TriList.SetSigTrueAction(2942, () => Press(V(ShiftMode))); - TriList.SetSigTrueAction(2943, () => Press(W(ShiftMode))); - TriList.SetSigTrueAction(2944, () => Press(X(ShiftMode))); - TriList.SetSigTrueAction(2945, () => Press(Y(ShiftMode))); - TriList.SetSigTrueAction(2946, () => Press(Z(ShiftMode))); - TriList.SetSigTrueAction(2947, () => Press('.')); - TriList.SetSigTrueAction(2948, () => Press('@')); - TriList.SetSigTrueAction(2949, () => Press(' ')); - TriList.SetSigHeldAction(2950, 500, StartBackspaceRepeat, StopBackspaceRepeat, Backspace); - //TriList.SetSigTrueAction(2950, Backspace); - TriList.SetSigTrueAction(2951, Shift); - TriList.SetSigTrueAction(2952, NumShift); - TriList.SetSigTrueAction(2953, Clear); - TriList.SetSigTrueAction(2954, () => Press(DotComButtonString)); + /// + /// Gets or sets the OutputFeedback + /// + public StringFeedback OutputFeedback { get; private set; } - TriList.SetBool(GoButtonVisibleJoin, GoButtonVisible); - TriList.SetString(GoButtonTextJoin, GoButtonText); - TriList.SetBool(SecondaryButtonVisibleJoin, SecondaryButtonVisible); - TriList.SetString(SecondaryButtonTextJoin, SecondaryButtonText); + /// + /// Gets or sets the IsVisible + /// + public bool IsVisible { get; private set; } - TriList.SetBool(KeyboardVisible, true); - ShowKeys(); - IsVisible = true; - } + /// + /// Gets or sets the DotComButtonString + /// + public string DotComButtonString { get; set; } - /// - /// Hides the keyboard and disconnects ALL sig handlers from 2901 - 2969 - /// - public void Hide() - { - if (!IsVisible) - return; + /// + /// Gets or sets the GoButtonText + /// + public string GoButtonText { get; set; } - for (uint i = 2901; i < 2970; i++) - TriList.ClearBoolSigAction(i); + /// + /// Gets or sets the SecondaryButtonText + /// + public string SecondaryButtonText { get; set; } - // run attached actions - if (HideAction != null) - HideAction(); + /// + /// Gets or sets the GoButtonVisible + /// + public bool GoButtonVisible { get; set; } - TriList.SetBool(KeyboardVisible, false); - IsVisible = false; - } + /// + /// Gets or sets the SecondaryButtonVisible + /// + public bool SecondaryButtonVisible { get; set; } - /// - /// - /// - /// - public void Press(char c) - { - OnKeyPress(c.ToString()); - Output.Append(c); - OutputFeedback.FireUpdate(); - ResetShift(); - } + int ShiftMode = 0; + + StringBuilder Output; - /// - /// - /// - /// - public void Press(string s) - { - OnKeyPress(s); - Output.Append(s); - OutputFeedback.FireUpdate(); - ResetShift(); - } + /// + /// Gets or sets the HideAction + /// + public Action HideAction { get; set; } - /// - /// - /// - public void EnableGoButton() - { - TriList.SetBool(GoButtonEnableJoin, true); - } + CTimer BackspaceTimer; - /// - /// - /// - public void DisableGoButton() - { - TriList.SetBool(GoButtonEnableJoin, false); - } - - void ResetShift() - { - if (ShiftMode == 1) + /// + /// + /// + /// + public HabaneroKeyboardController(BasicTriList trilist) { - ShiftMode = 0; + TriList = trilist; + Output = new StringBuilder(); + OutputFeedback = new StringFeedback(() => Output.ToString()); + DotComButtonString = ".com"; + } + + /// + /// Show method + /// + public void Show() + { + if (IsVisible) + return; + + TriList.SetSigTrueAction(ClosePressJoin, Hide); + TriList.SetSigTrueAction(GoButtonPressJoin, () => OnKeyPress(KeyboardSpecialKey.GoButton)); + TriList.SetSigTrueAction(SecondaryButtonPressJoin, () => OnKeyPress(KeyboardSpecialKey.SecondaryButton)); + TriList.SetSigTrueAction(2921, () => Press(A(ShiftMode))); + TriList.SetSigTrueAction(2922, () => Press(B(ShiftMode))); + TriList.SetSigTrueAction(2923, () => Press(C(ShiftMode))); + TriList.SetSigTrueAction(2924, () => Press(D(ShiftMode))); + TriList.SetSigTrueAction(2925, () => Press(E(ShiftMode))); + TriList.SetSigTrueAction(2926, () => Press(F(ShiftMode))); + TriList.SetSigTrueAction(2927, () => Press(G(ShiftMode))); + TriList.SetSigTrueAction(2928, () => Press(H(ShiftMode))); + TriList.SetSigTrueAction(2929, () => Press(I(ShiftMode))); + TriList.SetSigTrueAction(2930, () => Press(J(ShiftMode))); + TriList.SetSigTrueAction(2931, () => Press(K(ShiftMode))); + TriList.SetSigTrueAction(2932, () => Press(L(ShiftMode))); + TriList.SetSigTrueAction(2933, () => Press(M(ShiftMode))); + TriList.SetSigTrueAction(2934, () => Press(N(ShiftMode))); + TriList.SetSigTrueAction(2935, () => Press(O(ShiftMode))); + TriList.SetSigTrueAction(2936, () => Press(P(ShiftMode))); + TriList.SetSigTrueAction(2937, () => Press(Q(ShiftMode))); + TriList.SetSigTrueAction(2938, () => Press(R(ShiftMode))); + TriList.SetSigTrueAction(2939, () => Press(S(ShiftMode))); + TriList.SetSigTrueAction(2940, () => Press(T(ShiftMode))); + TriList.SetSigTrueAction(2941, () => Press(U(ShiftMode))); + TriList.SetSigTrueAction(2942, () => Press(V(ShiftMode))); + TriList.SetSigTrueAction(2943, () => Press(W(ShiftMode))); + TriList.SetSigTrueAction(2944, () => Press(X(ShiftMode))); + TriList.SetSigTrueAction(2945, () => Press(Y(ShiftMode))); + TriList.SetSigTrueAction(2946, () => Press(Z(ShiftMode))); + TriList.SetSigTrueAction(2947, () => Press('.')); + TriList.SetSigTrueAction(2948, () => Press('@')); + TriList.SetSigTrueAction(2949, () => Press(' ')); + TriList.SetSigHeldAction(2950, 500, StartBackspaceRepeat, StopBackspaceRepeat, Backspace); + //TriList.SetSigTrueAction(2950, Backspace); + TriList.SetSigTrueAction(2951, Shift); + TriList.SetSigTrueAction(2952, NumShift); + TriList.SetSigTrueAction(2953, Clear); + TriList.SetSigTrueAction(2954, () => Press(DotComButtonString)); + + TriList.SetBool(GoButtonVisibleJoin, GoButtonVisible); + TriList.SetString(GoButtonTextJoin, GoButtonText); + TriList.SetBool(SecondaryButtonVisibleJoin, SecondaryButtonVisible); + TriList.SetString(SecondaryButtonTextJoin, SecondaryButtonText); + + TriList.SetBool(KeyboardVisible, true); ShowKeys(); + IsVisible = true; } - else if (ShiftMode == 3) + + /// + /// Hide method + /// + public void Hide() { - ShiftMode = 2; - ShowKeys(); + if (!IsVisible) + return; + + for (uint i = 2901; i < 2970; i++) + TriList.ClearBoolSigAction(i); + + // run attached actions + if(HideAction != null) + HideAction(); + + TriList.SetBool(KeyboardVisible, false); + IsVisible = false; } - } - char A(int i) { return new char[] { 'a', 'A', '?', '?' }[i]; } - char B(int i) { return new char[] { 'b', 'B', ':', ':' }[i]; } - char C(int i) { return new char[] { 'c', 'C', '>', '>' }[i]; } - char D(int i) { return new char[] { 'd', 'D', '_', '_' }[i]; } - char E(int i) { return new char[] { 'e', 'E', '3', '#' }[i]; } - char F(int i) { return new char[] { 'f', 'F', '=', '=' }[i]; } - char G(int i) { return new char[] { 'g', 'G', '+', '+' }[i]; } - char H(int i) { return new char[] { 'h', 'H', '[', '[' }[i]; } - char I(int i) { return new char[] { 'i', 'I', '8', '*' }[i]; } - char J(int i) { return new char[] { 'j', 'J', ']', ']' }[i]; } - char K(int i) { return new char[] { 'k', 'K', '/', '/' }[i]; } - char L(int i) { return new char[] { 'l', 'L', '\\', '\\' }[i]; } - char M(int i) { return new char[] { 'm', 'M', '"', '"' }[i]; } - char N(int i) { return new char[] { 'n', 'N', '\'', '\'' }[i]; } - char O(int i) { return new char[] { 'o', 'O', '9', '(' }[i]; } - char P(int i) { return new char[] { 'p', 'P', '0', ')' }[i]; } - char Q(int i) { return new char[] { 'q', 'Q', '1', '!' }[i]; } - char R(int i) { return new char[] { 'r', 'R', '4', '$' }[i]; } - char S(int i) { return new char[] { 's', 'S', '-', '-' }[i]; } - char T(int i) { return new char[] { 't', 'T', '5', '%' }[i]; } - char U(int i) { return new char[] { 'u', 'U', '7', '&' }[i]; } - char V(int i) { return new char[] { 'v', 'V', ';', ';' }[i]; } - char W(int i) { return new char[] { 'w', 'W', '2', '@' }[i]; } - char X(int i) { return new char[] { 'x', 'X', '<', '<' }[i]; } - char Y(int i) { return new char[] { 'y', 'Y', '6', '^' }[i]; } - char Z(int i) { return new char[] { 'z', 'Z', ',', ',' }[i]; } - - /// - /// Does what it says - /// - void StartBackspaceRepeat() - { - if (BackspaceTimer == null) + /// + /// + /// + /// + /// + /// Press method + /// + public void Press(char c) { - BackspaceTimer = new Timer(175) { AutoReset = true }; - BackspaceTimer.Elapsed += (s, e) => Backspace(); - BackspaceTimer.Start(); - Backspace(); + OnKeyPress(c.ToString()); + Output.Append(c); + OutputFeedback.FireUpdate(); + ResetShift(); } - } - /// - /// Does what it says - /// - void StopBackspaceRepeat() - { - if (BackspaceTimer != null) + /// + /// + /// + /// + /// + /// Press method + /// + public void Press(string s) { - BackspaceTimer.Stop(); - BackspaceTimer = null; + OnKeyPress(s); + Output.Append(s); + OutputFeedback.FireUpdate(); + ResetShift(); } - } - void Backspace() - { - OnKeyPress(KeyboardSpecialKey.Backspace); - - if (Output.Length > 0) + /// + /// EnableGoButton method + /// + public void EnableGoButton() { - Output.Remove(Output.Length - 1, 1); + TriList.SetBool(GoButtonEnableJoin, true); + } + + /// + /// + /// + public void DisableGoButton() + { + TriList.SetBool(GoButtonEnableJoin, false); + } + + void ResetShift() + { + if (ShiftMode == 1) + { + ShiftMode = 0; + ShowKeys(); + } + else if (ShiftMode == 3) + { + ShiftMode = 2; + ShowKeys(); + } + } + + char A(int i) { return new char[] { 'a', 'A', '?', '?' }[i]; } + char B(int i) { return new char[] { 'b', 'B', ':', ':' }[i]; } + char C(int i) { return new char[] { 'c', 'C', '>', '>' }[i]; } + char D(int i) { return new char[] { 'd', 'D', '_', '_' }[i]; } + char E(int i) { return new char[] { 'e', 'E', '3', '#' }[i]; } + char F(int i) { return new char[] { 'f', 'F', '=', '=' }[i]; } + char G(int i) { return new char[] { 'g', 'G', '+', '+' }[i]; } + char H(int i) { return new char[] { 'h', 'H', '[', '[' }[i]; } + char I(int i) { return new char[] { 'i', 'I', '8', '*' }[i]; } + char J(int i) { return new char[] { 'j', 'J', ']', ']' }[i]; } + char K(int i) { return new char[] { 'k', 'K', '/', '/' }[i]; } + char L(int i) { return new char[] { 'l', 'L', '\\', '\\' }[i]; } + char M(int i) { return new char[] { 'm', 'M', '"', '"' }[i]; } + char N(int i) { return new char[] { 'n', 'N', '\'', '\'' }[i]; } + char O(int i) { return new char[] { 'o', 'O', '9', '(' }[i]; } + char P(int i) { return new char[] { 'p', 'P', '0', ')' }[i]; } + char Q(int i) { return new char[] { 'q', 'Q', '1', '!' }[i]; } + char R(int i) { return new char[] { 'r', 'R', '4', '$' }[i]; } + char S(int i) { return new char[] { 's', 'S', '-', '-' }[i]; } + char T(int i) { return new char[] { 't', 'T', '5', '%' }[i]; } + char U(int i) { return new char[] { 'u', 'U', '7', '&' }[i]; } + char V(int i) { return new char[] { 'v', 'V', ';', ';' }[i]; } + char W(int i) { return new char[] { 'w', 'W', '2', '@' }[i]; } + char X(int i) { return new char[] { 'x', 'X', '<', '<' }[i]; } + char Y(int i) { return new char[] { 'y', 'Y', '6', '^' }[i]; } + char Z(int i) { return new char[] { 'z', 'Z', ',', ',' }[i]; } + + /// + /// Does what it says + /// + void StartBackspaceRepeat() + { + if (BackspaceTimer == null) + { + BackspaceTimer = new CTimer(o => Backspace(), null, 0, 175); + } + } + + /// + /// Does what it says + /// + void StopBackspaceRepeat() + { + if (BackspaceTimer != null) + { + BackspaceTimer.Stop(); + BackspaceTimer = null; + } + } + + void Backspace() + { + OnKeyPress(KeyboardSpecialKey.Backspace); + + if (Output.Length > 0) + { + Output.Remove(Output.Length - 1, 1); + OutputFeedback.FireUpdate(); + } + } + + void Clear() + { + OnKeyPress(KeyboardSpecialKey.Clear); + + Output.Remove(0, Output.Length); OutputFeedback.FireUpdate(); } + + /* When in mode 0 (lowercase): + * shift button: up arrow 0 + * numShift button: 123/#$@#$ 0 + * + * - shift --> mode 1 + * - double-tap shift --> caps lock + * - numShift --> mode 2 + * + * mode 1 (uppercase) + * shift button: down arrow 1 + * numShift button: 123/##$# 0 + * + * - shift --> mode 0 + * - numShift --> mode 2 + * + * - Tapping any key will go back to mode 0 + * + * mode 2 (numbers-sym) + * Shift button: #$#$#$ 2 + * numShift: ABC 1 + * + * - shift --> mode 3 + * - double-tap shift --> caps lock + * - numShift --> mode 0 + * + * mode 3 (sym) + * Shift button: 123 3 + * numShift: ABC 1 + * + * - shift --> mode 2 + * - numShift --> mode 0 + * + * - Tapping any key will go back to mode 2 + */ + void Shift() + { + if (ShiftMode == 0) + ShiftMode = 1; + else if (ShiftMode == 1) + ShiftMode = 0; + else if (ShiftMode == 2) + ShiftMode = 3; + else + ShiftMode = 2; + + ShowKeys(); + } + + void NumShift() + { + if (ShiftMode == 0 || ShiftMode == 1) + ShiftMode = 2; + else if (ShiftMode == 2 || ShiftMode == 3) + ShiftMode = 0; + ShowKeys(); + } + + void ShowKeys() + { + TriList.SetString(2921, A(ShiftMode).ToString()); + TriList.SetString(2922, B(ShiftMode).ToString()); + TriList.SetString(2923, C(ShiftMode).ToString()); + TriList.SetString(2924, D(ShiftMode).ToString()); + TriList.SetString(2925, E(ShiftMode).ToString()); + TriList.SetString(2926, F(ShiftMode).ToString()); + TriList.SetString(2927, G(ShiftMode).ToString()); + TriList.SetString(2928, H(ShiftMode).ToString()); + TriList.SetString(2929, I(ShiftMode).ToString()); + TriList.SetString(2930, J(ShiftMode).ToString()); + TriList.SetString(2931, K(ShiftMode).ToString()); + TriList.SetString(2932, L(ShiftMode).ToString()); + TriList.SetString(2933, M(ShiftMode).ToString()); + TriList.SetString(2934, N(ShiftMode).ToString()); + TriList.SetString(2935, O(ShiftMode).ToString()); + TriList.SetString(2936, P(ShiftMode).ToString()); + TriList.SetString(2937, Q(ShiftMode).ToString()); + TriList.SetString(2938, R(ShiftMode).ToString()); + TriList.SetString(2939, S(ShiftMode).ToString()); + TriList.SetString(2940, T(ShiftMode).ToString()); + TriList.SetString(2941, U(ShiftMode).ToString()); + TriList.SetString(2942, V(ShiftMode).ToString()); + TriList.SetString(2943, W(ShiftMode).ToString()); + TriList.SetString(2944, X(ShiftMode).ToString()); + TriList.SetString(2945, Y(ShiftMode).ToString()); + TriList.SetString(2946, Z(ShiftMode).ToString()); + TriList.SetString(2954, DotComButtonString); + + TriList.SetUshort(2951, (ushort)ShiftMode); // 0 = up, 1 = down, 2 = #, 3 = 123 + TriList.SetUshort(2952, (ushort)(ShiftMode < 2 ? 0 : 1)); // 0 = #, 1 = abc + } + + /// + /// Event fire helper for text + /// + /// + void OnKeyPress(string text) + { + var handler = KeyPress; + if (handler != null) + KeyPress(this, new KeyboardControllerPressEventArgs(text)); + } + + /// + /// event helper for special keys + /// + /// + void OnKeyPress(KeyboardSpecialKey key) + { + var handler = KeyPress; + if (handler != null) + KeyPress(this, new KeyboardControllerPressEventArgs(key)); + } + + + /// + /// 2901 + /// + public const uint KeyboardVisible = 2901; + /// + /// 2902 + /// + public const uint ClosePressJoin = 2902; + /// + /// 2903 + /// + public const uint GoButtonPressJoin = 2903; + /// + /// 2903 + /// + public const uint GoButtonTextJoin = 2903; + /// + /// 2904 + /// + public const uint SecondaryButtonPressJoin = 2904; + /// + /// 2904 + /// + public const uint SecondaryButtonTextJoin = 2904; + /// + /// 2905 + /// + public const uint GoButtonVisibleJoin = 2905; + /// + /// 2906 + /// + public const uint SecondaryButtonVisibleJoin = 2906; + /// + /// 2907 + /// + public const uint GoButtonEnableJoin = 2907; + /// + /// 2910 + /// + public const uint ClearPressJoin = 2910; + /// + /// 2911 + /// + public const uint ClearVisibleJoin = 2911; + } - void Clear() + /// + /// Event args for keyboard key presses + /// + public class KeyboardControllerPressEventArgs : EventArgs { - OnKeyPress(KeyboardSpecialKey.Clear); + /// + /// Gets or sets the Text + /// + public string Text { get; private set; } - Output.Remove(0, Output.Length); - OutputFeedback.FireUpdate(); + /// + /// Gets or sets the SpecialKey + /// + public KeyboardSpecialKey SpecialKey { get; private set; } + + /// + /// Constructor + /// + /// + public KeyboardControllerPressEventArgs(string text) + { + Text = text; + } + + /// + /// Constructor + /// + /// special keyboard key + public KeyboardControllerPressEventArgs(KeyboardSpecialKey key) + { + SpecialKey = key; + } } - /* When in mode 0 (lowercase): - * shift button: up arrow 0 - * numShift button: 123/#$@#$ 0 - * - * - shift --> mode 1 - * - double-tap shift --> caps lock - * - numShift --> mode 2 - * - * mode 1 (uppercase) - * shift button: down arrow 1 - * numShift button: 123/##$# 0 - * - * - shift --> mode 0 - * - numShift --> mode 2 - * - * - Tapping any key will go back to mode 0 - * - * mode 2 (numbers-sym) - * Shift button: #$#$#$ 2 - * numShift: ABC 1 - * - * - shift --> mode 3 - * - double-tap shift --> caps lock - * - numShift --> mode 0 - * - * mode 3 (sym) - * Shift button: 123 3 - * numShift: ABC 1 - * - * - shift --> mode 2 - * - numShift --> mode 0 - * - * - Tapping any key will go back to mode 2 - */ - void Shift() + /// + /// Enumeration of KeyboardSpecialKey values + /// + public enum KeyboardSpecialKey { - if (ShiftMode == 0) - ShiftMode = 1; - else if (ShiftMode == 1) - ShiftMode = 0; - else if (ShiftMode == 2) - ShiftMode = 3; - else - ShiftMode = 2; - - ShowKeys(); + /// + /// None + /// + None = 0, + + /// + /// Backspace + /// + Backspace, + + /// + /// Clear + /// + Clear, + + /// + /// GoButton + /// + GoButton, + + /// + /// SecondaryButton + /// + SecondaryButton } - - void NumShift() - { - if (ShiftMode == 0 || ShiftMode == 1) - ShiftMode = 2; - else if (ShiftMode == 2 || ShiftMode == 3) - ShiftMode = 0; - ShowKeys(); - } - - void ShowKeys() - { - TriList.SetString(2921, A(ShiftMode).ToString()); - TriList.SetString(2922, B(ShiftMode).ToString()); - TriList.SetString(2923, C(ShiftMode).ToString()); - TriList.SetString(2924, D(ShiftMode).ToString()); - TriList.SetString(2925, E(ShiftMode).ToString()); - TriList.SetString(2926, F(ShiftMode).ToString()); - TriList.SetString(2927, G(ShiftMode).ToString()); - TriList.SetString(2928, H(ShiftMode).ToString()); - TriList.SetString(2929, I(ShiftMode).ToString()); - TriList.SetString(2930, J(ShiftMode).ToString()); - TriList.SetString(2931, K(ShiftMode).ToString()); - TriList.SetString(2932, L(ShiftMode).ToString()); - TriList.SetString(2933, M(ShiftMode).ToString()); - TriList.SetString(2934, N(ShiftMode).ToString()); - TriList.SetString(2935, O(ShiftMode).ToString()); - TriList.SetString(2936, P(ShiftMode).ToString()); - TriList.SetString(2937, Q(ShiftMode).ToString()); - TriList.SetString(2938, R(ShiftMode).ToString()); - TriList.SetString(2939, S(ShiftMode).ToString()); - TriList.SetString(2940, T(ShiftMode).ToString()); - TriList.SetString(2941, U(ShiftMode).ToString()); - TriList.SetString(2942, V(ShiftMode).ToString()); - TriList.SetString(2943, W(ShiftMode).ToString()); - TriList.SetString(2944, X(ShiftMode).ToString()); - TriList.SetString(2945, Y(ShiftMode).ToString()); - TriList.SetString(2946, Z(ShiftMode).ToString()); - TriList.SetString(2954, DotComButtonString); - - TriList.SetUshort(2951, (ushort)ShiftMode); // 0 = up, 1 = down, 2 = #, 3 = 123 - TriList.SetUshort(2952, (ushort)(ShiftMode < 2 ? 0 : 1)); // 0 = #, 1 = abc - } - - /// - /// Event fire helper for text - /// - /// - void OnKeyPress(string text) - { - var handler = KeyPress; - if (handler != null) - KeyPress(this, new KeyboardControllerPressEventArgs(text)); - } - - /// - /// event helper for special keys - /// - /// - void OnKeyPress(KeyboardSpecialKey key) - { - var handler = KeyPress; - if (handler != null) - KeyPress(this, new KeyboardControllerPressEventArgs(key)); - } - - - /// - /// 2901 - /// - public const uint KeyboardVisible = 2901; - /// - /// 2902 - /// - public const uint ClosePressJoin = 2902; - /// - /// 2903 - /// - public const uint GoButtonPressJoin = 2903; - /// - /// 2903 - /// - public const uint GoButtonTextJoin = 2903; - /// - /// 2904 - /// - public const uint SecondaryButtonPressJoin = 2904; - /// - /// 2904 - /// - public const uint SecondaryButtonTextJoin = 2904; - /// - /// 2905 - /// - public const uint GoButtonVisibleJoin = 2905; - /// - /// 2906 - /// - public const uint SecondaryButtonVisibleJoin = 2906; - /// - /// 2907 - /// - public const uint GoButtonEnableJoin = 2907; - /// - /// 2910 - /// - public const uint ClearPressJoin = 2910; - /// - /// 2911 - /// - public const uint ClearVisibleJoin = 2911; - -} - -/// -/// -/// -public class KeyboardControllerPressEventArgs : EventArgs -{ - /// - /// The text of the key that was pressed. This will be null if a special key (backspace, clear, go, secondary) was pressed, in which case the SpecialKey property should be checked to determine which key was pressed. - /// - public string Text { get; private set; } - - /// - /// If a special key (backspace, clear, go, secondary) was pressed, this property indicates which key it was. If a regular key was pressed, this will be KeyboardSpecialKey.None, and the Text property should be checked for the value of the key press. - /// - public KeyboardSpecialKey SpecialKey { get; private set; } - - /// - /// Constructor for regular key presses - /// - public KeyboardControllerPressEventArgs(string text) - { - Text = text; - } - - /// - /// Constructor for special key presses - /// - /// - public KeyboardControllerPressEventArgs(KeyboardSpecialKey key) - { - SpecialKey = key; - } -} - -/// -/// An enum representing special keys on the keyboard that don't have text values, such as backspace, clear, go, and the secondary button. The None value is used for regular key presses that do have text values, and should not be used for special key presses. -/// -public enum KeyboardSpecialKey -{ - /// - /// Indicates that a regular key with a text value was pressed, rather than a special key. When this value is set, the Text property of the KeyboardControllerPressEventArgs should be checked to get the value of the key press. - /// - None = 0, - - /// - /// Indicates that the backspace key was pressed. - /// - Backspace, - /// - /// Indicates that the clear key was pressed. - /// - Clear, - /// - /// Indicates that the go button was pressed. - /// - GoButton, - /// - /// Indicates that the secondary button was pressed. - /// - SecondaryButton } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs b/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs index f3bd2e89..e3545cd0 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs @@ -1,10 +1,14 @@ using System; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ + /// + /// Represents a ModalDialog + /// public class ModalDialog { /// @@ -15,10 +19,10 @@ namespace PepperDash.Essentials.Core; /// Bool press 3992 /// public const uint Button2Join = 3992; - /// - /// 3993 - /// - public const uint CancelButtonJoin = 3993; + /// + /// 3993 + /// + public const uint CancelButtonJoin = 3993; /// ///For visibility of single button. Bool feedback 3994 /// @@ -31,19 +35,19 @@ namespace PepperDash.Essentials.Core; /// Shows the timer guage if in use. Bool feedback 3996 /// public const uint TimerVisibleJoin = 3996; - /// - /// Visibility join to show "X" button 3997 - /// - public const uint CancelVisibleJoin = 3997; + /// + /// Visibility join to show "X" button 3997 + /// + public const uint CancelVisibleJoin = 3997; /// /// Shows the modal subpage. Boolean feeback join 3999 /// public const uint ModalVisibleJoin = 3999; - /// - /// The seconds value of the countdown timer. Ushort join 3991 - /// - //public const uint TimerSecondsJoin = 3991; + ///// + ///// The seconds value of the countdown timer. Ushort join 3991 + ///// + //public const uint TimerSecondsJoin = 3991; /// /// The full ushort value of the countdown timer for a gauge. Ushort join 3992 /// @@ -78,10 +82,10 @@ namespace PepperDash.Essentials.Core; get { return TriList.BooleanInput[ModalVisibleJoin].BoolValue; } } - /// - /// - /// - public bool CanCancel { get; private set; } + /// + /// + /// + public bool CanCancel { get; private set; } BasicTriList TriList; @@ -99,10 +103,10 @@ namespace PepperDash.Essentials.Core; TriList = triList; // Attach actions to buttons - triList.SetSigFalseAction(Button1Join, () => OnModalComplete(1)); + triList.SetSigFalseAction(Button1Join, () => OnModalComplete(1)); triList.SetSigFalseAction(Button2Join, () => OnModalComplete(2)); - triList.SetSigFalseAction(CancelButtonJoin, () => { if (CanCancel) CancelDialog(); }); - CanCancel = true; + triList.SetSigFalseAction(CancelButtonJoin, () => { if (CanCancel) CancelDialog(); }); + CanCancel = true; } /// @@ -152,15 +156,15 @@ namespace PepperDash.Essentials.Core; TriList.StringInput[Button2TextJoin].StringValue = button2Text; } // Show/hide guage - TriList.BooleanInput[TimerVisibleJoin].BoolValue = showGauge; - - CanCancel = showCancel; - TriList.BooleanInput[CancelVisibleJoin].BoolValue = showCancel; + TriList.BooleanInput[TimerVisibleJoin].BoolValue = showGauge; + + CanCancel = showCancel; + TriList.BooleanInput[CancelVisibleJoin].BoolValue = showCancel; //Reveal and activate TriList.BooleanInput[ModalVisibleJoin].BoolValue = true; - WakePanel(); + WakePanel(); return true; } @@ -168,47 +172,49 @@ namespace PepperDash.Essentials.Core; return false; } - /// - /// Wakes the panel by turning on the backlight if off - /// - public void WakePanel() - { - try - { - var panel = TriList as TswFt5Button; - - if (panel != null && panel.ExtenderSystemReservedSigs.BacklightOffFeedback.BoolValue) - panel.ExtenderSystemReservedSigs.BacklightOn(); - } - catch - { - Debug.LogMessage(LogEventLevel.Debug, "Error Waking Panel. Maybe testing with Xpanel?"); - } - } - - /// - /// Hide dialog from elsewhere, fires CompleteAction - /// - public void CancelDialog() + /// + /// WakePanel method + /// + public void WakePanel() { - OnModalComplete(0); + try + { + var panel = TriList as TswFt5Button; + + if (panel != null && panel.ExtenderSystemReservedSigs.BacklightOffFeedback.BoolValue) + panel.ExtenderSystemReservedSigs.BacklightOn(); + } + catch + { + Debug.LogMessage(LogEventLevel.Debug, "Error Waking Panel. Maybe testing with Xpanel?"); + } } - /// - /// Hides dialog. Fires no action - /// - public void HideDialog() - { - TriList.BooleanInput[ModalVisibleJoin].BoolValue = false; - } + /// + /// CancelDialog method + /// + public void CancelDialog() + { + OnModalComplete(0); + } + + /// + /// Hides dialog. Fires no action + /// + public void HideDialog() + { + TriList.BooleanInput[ModalVisibleJoin].BoolValue = false; + } // When the modal is cleared or times out, clean up the various bits void OnModalComplete(uint buttonNum) { TriList.BooleanInput[ModalVisibleJoin].BoolValue = false; - var action = ModalCompleteAction; - if (action != null) - action(buttonNum); + var action = ModalCompleteAction; + if (action != null) + action(buttonNum); } - } \ No newline at end of file + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs b/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs index 2fb8cb59..92764f22 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs @@ -7,8 +7,8 @@ using PepperDash.Core; using PepperDash.Core.Logging; using Serilog.Events; -namespace PepperDash.Essentials.Core.Touchpanels; - +namespace PepperDash.Essentials.Core.Touchpanels +{ /// /// A wrapper class for the touchpanel portion of an MPC3 class process to allow for configurable /// behavior of the keybad buttons @@ -85,9 +85,9 @@ namespace PepperDash.Essentials.Core.Touchpanels; return; } - TryParseInt(key, out int buttonNumber); + TryParseInt(key, out int buttonNumber); - var buttonEventTypes = config.EventTypes; + var buttonEventTypes = config.EventTypes; BoolOutputSig enabledFb = null; BoolOutputSig disabledFb = null; @@ -174,10 +174,10 @@ namespace PepperDash.Essentials.Core.Touchpanels; return; } - TryParseInt(key, out int buttonNumber); + TryParseInt(key, out int buttonNumber); - // Link up the button feedbacks to the specified device feedback - var buttonFeedback = config.Feedback; + // Link up the button feedbacks to the specified device feedback + var buttonFeedback = config.Feedback; if (buttonFeedback == null || string.IsNullOrEmpty(buttonFeedback.DeviceKey)) { Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedback not configured, skipping.", @@ -189,14 +189,14 @@ namespace PepperDash.Essentials.Core.Touchpanels; try { - if (!(DeviceManager.GetDeviceForKey(buttonFeedback.DeviceKey) is Device device)) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedback deviceKey '{1}' not found.", - key, buttonFeedback.DeviceKey); - return; - } + if (!(DeviceManager.GetDeviceForKey(buttonFeedback.DeviceKey) is Device device)) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedback deviceKey '{1}' not found.", + key, buttonFeedback.DeviceKey); + return; + } - deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); + deviceFeedback = device.GetFeedbackProperty(buttonFeedback.FeedbackName); if (deviceFeedback == null) { Debug.LogMessage(LogEventLevel.Debug, this, "Button '{0}' feedbackName property '{1}' not found.", @@ -236,36 +236,36 @@ namespace PepperDash.Essentials.Core.Touchpanels; var boolFeedback = deviceFeedback as BoolFeedback; - switch (key) - { - case ("power"): - { - boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackPower); - break; - } - case ("volumeup"): - case ("volumedown"): - case ("volumefeedback"): - { - if (deviceFeedback is IntFeedback intFeedback) + switch (key) + { + case ("power"): { - var volumeFeedback = intFeedback; - volumeFeedback.LinkInputSig(_touchpanel.VolumeBargraph); + boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackPower); + break; } - break; - } - case ("mute"): - { - boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackMute); - break; - } - default: - { - boolFeedback?.LinkCrestronFeedback(_touchpanel.Feedbacks[(uint)buttonNumber]); - break; - } + case ("volumeup"): + case ("volumedown"): + case ("volumefeedback"): + { + if (deviceFeedback is IntFeedback intFeedback) + { + var volumeFeedback = intFeedback; + volumeFeedback.LinkInputSig(_touchpanel.VolumeBargraph); + } + break; + } + case ("mute"): + { + boolFeedback?.LinkCrestronFeedback(_touchpanel.FeedbackMute); + break; + } + default: + { + boolFeedback?.LinkCrestronFeedback(_touchpanel.Feedbacks[(uint)buttonNumber]); + break; + } + } } - } /// /// Try parse int helper method @@ -393,4 +393,5 @@ namespace PepperDash.Essentials.Core.Touchpanels; /// [JsonProperty("feedbackName")] public string FeedbackName { get; set; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs b/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs index cc54fd22..2d026dbb 100644 --- a/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs +++ b/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs @@ -1,11 +1,16 @@ using System; -using System.Timers; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; +using PepperDash.Core; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Extensions used for more-clear attachment of Actions to user objects on sigs /// @@ -13,7 +18,7 @@ namespace PepperDash.Essentials.Core; { /// /// Attaches Action to Sig's user object and returns the same Sig. This provides no protection - /// from null sigs + /// from null sigs /// /// The BoolOutputSig to attach the Action to /// An action to run when sig is pressed and when released @@ -69,14 +74,14 @@ namespace PepperDash.Essentials.Core; return sig.SetBoolSigAction(b => { if (!b) a(); }); } - /// - /// Sets an action to a held sig - /// - /// The sig - public static BoolOutputSig SetSigHeldAction(this BasicTriList tl, uint sigNum, uint heldMs, Action heldAction) - { - return SetSigHeldAction(tl, sigNum, heldMs, heldAction, null); - } + /// + /// Sets an action to a held sig + /// + /// The sig + public static BoolOutputSig SetSigHeldAction(this BasicTriList tl, uint sigNum, uint heldMs, Action heldAction) + { + return SetSigHeldAction(tl, sigNum, heldMs, heldAction, null); + } /// /// Sets an action to a held sig as well as a released-without-hold action @@ -84,7 +89,7 @@ namespace PepperDash.Essentials.Core; /// public static BoolOutputSig SetSigHeldAction(this BoolOutputSig sig, uint heldMs, Action heldAction, Action holdReleasedAction, Action releaseAction) { - Timer heldTimer = null; + CTimer heldTimer = null; bool wasHeld = false; return sig.SetBoolSigAction(press => { @@ -92,8 +97,7 @@ namespace PepperDash.Essentials.Core; { wasHeld = false; // Could insert a pressed action here - heldTimer = new Timer(heldMs) { AutoReset = false }; - heldTimer.Elapsed += (s, e) => + heldTimer = new CTimer(o => { // if still held and there's an action if (sig.BoolValue && heldAction != null) @@ -102,8 +106,7 @@ namespace PepperDash.Essentials.Core; // Hold action here heldAction(); } - }; - heldTimer.Start(); + }, heldMs); } else if (!press && !wasHeld) // released, no hold { @@ -121,14 +124,17 @@ namespace PepperDash.Essentials.Core; } - /// - /// Sets an action to a held sig as well as a released-without-hold action - /// - /// The sig - public static BoolOutputSig SetSigHeldAction(this BasicTriList tl, uint sigNum, uint heldMs, Action heldAction, Action releaseAction) - { + /// + /// Sets an action to a held sig as well as a released-without-hold action + /// + /// The sig + /// + /// SetSigHeldAction method + /// + public static BoolOutputSig SetSigHeldAction(this BasicTriList tl, uint sigNum, uint heldMs, Action heldAction, Action releaseAction) + { return tl.BooleanOutput[sigNum].SetSigHeldAction(heldMs, heldAction, null, releaseAction); - } + } /// /// Sets an action to a held sig, an action for the release of hold, as well as a released-without-hold action @@ -226,35 +232,35 @@ namespace PepperDash.Essentials.Core; return ClearSigAction(tl.StringOutput[sigNum]) as StringOutputSig; } - /// - /// Clears all actions on all sigs - /// - public static void ClearAllSigActions(this BasicTriList t1) - { - foreach (var sig in t1.BooleanOutput) + /// + /// ClearAllSigActions method + /// + public static void ClearAllSigActions(this BasicTriList t1) { - ClearSigAction(sig); + foreach (var sig in t1.BooleanOutput) + { + ClearSigAction(sig); + } + + foreach (var sig in t1.UShortOutput) + { + ClearSigAction(sig); + } + + foreach (var sig in t1.StringOutput) + { + ClearSigAction(sig); + } } - foreach (var sig in t1.UShortOutput) + /// + /// SetBool method + /// + public static void SetBool(this BasicTriList tl, uint sigNum, bool value) { - ClearSigAction(sig); + tl.BooleanInput[sigNum].BoolValue = value; } - foreach (var sig in t1.StringOutput) - { - ClearSigAction(sig); - } - } - - /// - /// Helper method to set the value of a bool Sig on TriList - /// - public static void SetBool(this BasicTriList tl, uint sigNum, bool value) - { - tl.BooleanInput[sigNum].BoolValue = value; - } - /// /// Sends an true-false pulse to the sig /// @@ -276,21 +282,21 @@ namespace PepperDash.Essentials.Core; tl.BooleanInput[sigNum].Pulse(ms); } - /// - /// Helper method to set the value of a ushort Sig on TriList - /// - public static void SetUshort(this BasicTriList tl, uint sigNum, ushort value) - { - tl.UShortInput[sigNum].UShortValue = value; - } + /// + /// Helper method to set the value of a ushort Sig on TriList + /// + public static void SetUshort(this BasicTriList tl, uint sigNum, ushort value) + { + tl.UShortInput[sigNum].UShortValue = value; + } - /// - /// Helper method to set the value of a string Sig on TriList - /// - public static void SetString(this BasicTriList tl, uint sigNum, string value) - { - tl.StringInput[sigNum].StringValue = value; - } + /// + /// Helper method to set the value of a string Sig on TriList + /// + public static void SetString(this BasicTriList tl, uint sigNum, string value) + { + tl.StringInput[sigNum].StringValue = value; + } /// /// Helper method to set the value of a string Sig on TriList with encoding @@ -337,4 +343,5 @@ namespace PepperDash.Essentials.Core; { return tl.StringOutput[sigNum].StringValue; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/TriListBridges/HandlerBridge.cs b/src/PepperDash.Essentials.Core/TriListBridges/HandlerBridge.cs index bd90ede7..13676e2d 100644 --- a/src/PepperDash.Essentials.Core/TriListBridges/HandlerBridge.cs +++ b/src/PepperDash.Essentials.Core/TriListBridges/HandlerBridge.cs @@ -1,22 +1,32 @@ - -namespace PepperDash.Essentials.Core; -/// -/// Abstract base class for TriList handler bridges -/// -public abstract class HandlerBridge +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.DeviceSupport; + + +namespace PepperDash.Essentials.Core { /// - /// Gets or sets the IsAttached + /// Abstract base class for TriList handler bridges /// - public bool IsAttached { get; protected set; } + public abstract class HandlerBridge + { + /// + /// Gets or sets the IsAttached + /// + public bool IsAttached { get; protected set; } - /// - /// Attaches the handler to the panel's user objects - /// - public abstract void AttachToTriListOutputs(bool sendUpdate); + /// + /// Attaches the handler to the panel's user objects + /// + public abstract void AttachToTriListOutputs(bool sendUpdate); - /// - /// Removes the handler from the panel's user objects - /// - public abstract void DetachFromTriListOutputs(); + /// + /// Removes the handler from the panel's user objects + /// + public abstract void DetachFromTriListOutputs(); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI PageManagers/BlurayPageManager.cs b/src/PepperDash.Essentials.Core/UI PageManagers/BlurayPageManager.cs index d5c54a31..d14058bd 100644 --- a/src/PepperDash.Essentials.Core/UI PageManagers/BlurayPageManager.cs +++ b/src/PepperDash.Essentials.Core/UI PageManagers/BlurayPageManager.cs @@ -1,7 +1,11 @@ using Crestron.SimplSharpPro.DeviceSupport; +using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.PageManagers; - +namespace PepperDash.Essentials.Core.PageManagers +{ + /// + /// Represents a DiscPlayerMediumPageManager + /// public class DiscPlayerMediumPageManager : MediumLeftSwitchablePageManager { IDiscPlayerControls Player; @@ -50,4 +54,5 @@ namespace PepperDash.Essentials.Core.PageManagers; TriList.BooleanInput[BackingPageJoin].BoolValue = false; TriList.BooleanInput[LeftSubpageJoin].BoolValue = false; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI PageManagers/PageManager.cs b/src/PepperDash.Essentials.Core/UI PageManagers/PageManager.cs index 79d23f19..ed5f0fff 100644 --- a/src/PepperDash.Essentials.Core/UI PageManagers/PageManager.cs +++ b/src/PepperDash.Essentials.Core/UI PageManagers/PageManager.cs @@ -2,8 +2,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.PageManagers; - +namespace PepperDash.Essentials.Core.PageManagers +{ /// /// The PageManager classes are used to bridge a device to subpage /// visibility. @@ -152,4 +152,5 @@ namespace PepperDash.Essentials.Core.PageManagers; { return GetOffsetJoin(DisplayUiType); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxThreePanelPageManager.cs b/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxThreePanelPageManager.cs index ddf598a2..338f47dc 100644 --- a/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxThreePanelPageManager.cs +++ b/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxThreePanelPageManager.cs @@ -3,221 +3,226 @@ using System.Collections.Generic; using System.Linq; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; +using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Presets; using Serilog.Events; -namespace PepperDash.Essentials.Core.PageManagers; - -public class ThreePanelPlusOnePageManager : PageManager +namespace PepperDash.Essentials.Core.PageManagers { /// - /// The trilist + /// Represents a ThreePanelPlusOnePageManager /// - protected BasicTriListWithSmartObject TriList; - - /// - /// Gets or sets the Position5TabsId - /// - public uint Position5TabsId { get; set; } - - /// - /// Show the tabs on the third panel - /// - protected bool ShowPosition5Tabs; - - /// - /// Joins that are always visible when this manager is visible - /// - protected uint[] FixedVisibilityJoins; - - /// - /// Gets or sets the current visible item in position 5 - /// - protected uint CurrentVisiblePosition5Item; - - /// - /// Constructor - /// - /// - public ThreePanelPlusOnePageManager(BasicTriListWithSmartObject trilist) + public class ThreePanelPlusOnePageManager : PageManager { - TriList = trilist; - CurrentVisiblePosition5Item = 1; - } + /// + /// The trilist + /// + protected BasicTriListWithSmartObject TriList; + + /// + /// Gets or sets the Position5TabsId + /// + public uint Position5TabsId { get; set; } - /// - /// The joins for the switchable panel in position 5 - /// - Dictionary Position5SubpageJoins = new Dictionary + /// + /// Show the tabs on the third panel + /// + protected bool ShowPosition5Tabs; + + /// + /// Joins that are always visible when this manager is visible + /// + protected uint[] FixedVisibilityJoins; + + /// + /// Gets or sets the current visible item in position 5 + /// + protected uint CurrentVisiblePosition5Item; + + /// + /// Constructor + /// + /// + public ThreePanelPlusOnePageManager(BasicTriListWithSmartObject trilist) + { + TriList = trilist; + CurrentVisiblePosition5Item = 1; + } + + /// + /// The joins for the switchable panel in position 5 + /// + Dictionary Position5SubpageJoins = new Dictionary + { + { 1, 10053 }, + { 2, 10054 } + }; + + /// + /// + /// + public override void Show() + { + // Project the joins into corresponding sigs. + var fixedSigs = FixedVisibilityJoins.Select(u => TriList.BooleanInput[u]).ToList(); + foreach (var sig in fixedSigs) + sig.BoolValue = true; + + if (ShowPosition5Tabs) { - { 1, 10053 }, - { 2, 10054 } - }; + // Show selected tab + TriList.BooleanInput[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = true; + // hook up tab object + var tabSo = TriList.SmartObjects[Position5TabsId]; + tabSo.BooleanOutput["Tab Button 1 Press"].UserObject = new Action(b => { if (!b) ShowTab(1); }); + tabSo.BooleanOutput["Tab Button 2 Press"].UserObject = new Action(b => { if (!b) ShowTab(2); }); + tabSo.SigChange -= tabSo_SigChange; + tabSo.SigChange += tabSo_SigChange; + } + } - /// - /// - /// - public override void Show() - { - // Project the joins into corresponding sigs. - var fixedSigs = FixedVisibilityJoins.Select(u => TriList.BooleanInput[u]).ToList(); - foreach (var sig in fixedSigs) - sig.BoolValue = true; + void tabSo_SigChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.SmartObjectEventArgs args) + { + var uo = args.Sig.UserObject; + if(uo is Action) + (uo as Action)(args.Sig.BoolValue); + } - if (ShowPosition5Tabs) + /// + /// Hide method + /// + /// + public override void Hide() { - // Show selected tab - TriList.BooleanInput[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = true; - // hook up tab object - var tabSo = TriList.SmartObjects[Position5TabsId]; - tabSo.BooleanOutput["Tab Button 1 Press"].UserObject = new Action(b => { if (!b) ShowTab(1); }); - tabSo.BooleanOutput["Tab Button 2 Press"].UserObject = new Action(b => { if (!b) ShowTab(2); }); - tabSo.SigChange -= tabSo_SigChange; - tabSo.SigChange += tabSo_SigChange; + var fixedSigs = FixedVisibilityJoins.Select(u => TriList.BooleanInput[u]).ToList(); + foreach (var sig in fixedSigs) + sig.BoolValue = false; + if (ShowPosition5Tabs) + { + TriList.BooleanInput[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = false; + + //var tabSo = TriList.SmartObjects[Position5TabsId]; + //tabSo.BooleanOutput["Tab Button 1 Press"].UserObject = null; + //tabSo.BooleanOutput["Tab Button 2 Press"].UserObject = null; + } + } + + void ShowTab(uint number) + { + // Ignore re-presses + if (CurrentVisiblePosition5Item == number) return; + // Swap subpage + var bi = TriList.BooleanInput; + if (CurrentVisiblePosition5Item > 0) + bi[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = false; + CurrentVisiblePosition5Item = number; + bi[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = true; } } - void tabSo_SigChange(Crestron.SimplSharpPro.GenericBase currentDevice, Crestron.SimplSharpPro.SmartObjectEventArgs args) - { - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - } + /// - /// Hide method + /// Represents a SetTopBoxThreePanelPageManager /// - /// - public override void Hide() + public class SetTopBoxThreePanelPageManager : ThreePanelPlusOnePageManager { - var fixedSigs = FixedVisibilityJoins.Select(u => TriList.BooleanInput[u]).ToList(); - foreach (var sig in fixedSigs) - sig.BoolValue = false; - if (ShowPosition5Tabs) + ISetTopBoxControls SetTopBox; + DevicePresetsView PresetsView; + + /// + /// Gets or sets the DpadSmartObjectId + /// + public uint DpadSmartObjectId { get; set; } + + /// + /// Gets or sets the NumberPadSmartObjectId + /// + public uint NumberPadSmartObjectId { get; set; } + + /// + /// Gets or sets the PresetsSmartObjectId + /// + public uint PresetsSmartObjectId { get; set; } + + /// + /// A page manager for set top box that shows some combination of four different panels, + /// in three slots on the page. + /// + /// + /// + public SetTopBoxThreePanelPageManager(ISetTopBoxControls stb, BasicTriListWithSmartObject trilist) + : base(trilist) { - TriList.BooleanInput[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = false; + SetTopBox = stb; + TriList = trilist; - //var tabSo = TriList.SmartObjects[Position5TabsId]; - //tabSo.BooleanOutput["Tab Button 1 Press"].UserObject = null; - //tabSo.BooleanOutput["Tab Button 2 Press"].UserObject = null; - } - } + DpadSmartObjectId = 10011; + NumberPadSmartObjectId = 10014; + PresetsSmartObjectId = 10012; + Position5TabsId = 10081; - void ShowTab(uint number) - { - // Ignore re-presses - if (CurrentVisiblePosition5Item == number) return; - // Swap subpage - var bi = TriList.BooleanInput; - if (CurrentVisiblePosition5Item > 0) - bi[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = false; - CurrentVisiblePosition5Item = number; - bi[Position5SubpageJoins[CurrentVisiblePosition5Item]].BoolValue = true; - } -} + bool dpad = stb.HasDpad; + bool preset = stb.HasPresets; + bool dvr = stb.HasDvr; + bool numbers = stb.HasNumeric; + if (dpad && !preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10031, 10091 }; + else if (!dpad && preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10032, 10091 }; + else if (!dpad && !preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10033, 10091 }; + else if (!dpad && !preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10034, 10091 }; + else if (dpad && preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10042, 10021, 10092 }; + else if (dpad && !preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10043, 10021, 10092 }; + else if (dpad && !preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10021, 10092 }; + else if (!dpad && preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10043, 10022, 10092 }; + else if (!dpad && preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10022, 10092 }; + else if (!dpad && !preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10023, 10092 }; -/// -/// Represents a SetTopBoxThreePanelPageManager -/// -public class SetTopBoxThreePanelPageManager : ThreePanelPlusOnePageManager -{ - ISetTopBoxControls SetTopBox; - DevicePresetsView PresetsView; + else if (dpad && preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10053, 10032, 10011, 10093 }; + else if (dpad && preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10032, 10011, 10093 }; + else if (dpad && !preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10033, 10011, 10093 }; + else if (!dpad && preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10033, 10012, 10093 }; - /// - /// Gets or sets the DpadSmartObjectId - /// - public uint DpadSmartObjectId { get; set; } + else if (dpad && preset && dvr && numbers) + { + FixedVisibilityJoins = new uint[] { 10081, 10032, 10011, 10093 }; // special case + ShowPosition5Tabs = true; + } + // Bad config case + else + { + Debug.LogMessage(LogEventLevel.Debug, stb, "WARNING: Not configured to show any UI elements"); + FixedVisibilityJoins = new uint[] { 10091 }; + } - /// - /// Gets or sets the NumberPadSmartObjectId - /// - public uint NumberPadSmartObjectId { get; set; } - - /// - /// Gets or sets the PresetsSmartObjectId - /// - public uint PresetsSmartObjectId { get; set; } - - /// - /// A page manager for set top box that shows some combination of four different panels, - /// in three slots on the page. - /// - /// - /// - public SetTopBoxThreePanelPageManager(ISetTopBoxControls stb, BasicTriListWithSmartObject trilist) - : base(trilist) - { - SetTopBox = stb; - TriList = trilist; - - DpadSmartObjectId = 10011; - NumberPadSmartObjectId = 10014; - PresetsSmartObjectId = 10012; - Position5TabsId = 10081; - - bool dpad = stb.HasDpad; - bool preset = stb.HasPresets; - bool dvr = stb.HasDvr; - bool numbers = stb.HasNumeric; - - if (dpad && !preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10031, 10091 }; - else if (!dpad && preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10032, 10091 }; - else if (!dpad && !preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10033, 10091 }; - else if (!dpad && !preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10034, 10091 }; - - else if (dpad && preset && !dvr && !numbers) FixedVisibilityJoins = new uint[] { 10042, 10021, 10092 }; - else if (dpad && !preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10043, 10021, 10092 }; - else if (dpad && !preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10021, 10092 }; - else if (!dpad && preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10043, 10022, 10092 }; - else if (!dpad && preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10022, 10092 }; - else if (!dpad && !preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10044, 10023, 10092 }; - - else if (dpad && preset && dvr && !numbers) FixedVisibilityJoins = new uint[] { 10053, 10032, 10011, 10093 }; - else if (dpad && preset && !dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10032, 10011, 10093 }; - else if (dpad && !preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10033, 10011, 10093 }; - else if (!dpad && preset && dvr && numbers) FixedVisibilityJoins = new uint[] { 10054, 10033, 10012, 10093 }; - - else if (dpad && preset && dvr && numbers) - { - FixedVisibilityJoins = new uint[] { 10081, 10032, 10011, 10093 }; // special case - ShowPosition5Tabs = true; - } - // Bad config case - else - { - Debug.LogMessage(LogEventLevel.Debug, stb, "WARNING: Not configured to show any UI elements"); - FixedVisibilityJoins = new uint[] { 10091 }; + // Build presets + if (stb.HasPresets && stb.TvPresets != null) + { + PresetsView = new DevicePresetsView(trilist, stb.TvPresets); + } } - // Build presets - if (stb.HasPresets && stb.TvPresets != null) + /// + /// Show method + /// + /// + public override void Show() { - PresetsView = new DevicePresetsView(trilist, stb.TvPresets); + if(PresetsView != null) + PresetsView.Attach(); + base.Show(); } - } - /// - /// Show method - /// - /// - public override void Show() - { - if (PresetsView != null) - PresetsView.Attach(); - base.Show(); - } - - /// - /// Hide method - /// - /// - public override void Hide() - { - if (PresetsView != null) - PresetsView.Detach(); - base.Hide(); + /// + /// Hide method + /// + /// + public override void Hide() + { + if (PresetsView != null) + PresetsView.Detach(); + base.Hide(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxTwoPanelPageManager.cs b/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxTwoPanelPageManager.cs index 83482e88..0e5b520a 100644 --- a/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxTwoPanelPageManager.cs +++ b/src/PepperDash.Essentials.Core/UI PageManagers/SetTopBoxTwoPanelPageManager.cs @@ -2,8 +2,8 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Presets; -namespace PepperDash.Essentials.Core.PageManagers; - +namespace PepperDash.Essentials.Core.PageManagers +{ /// /// Represents a SetTopBoxMediumPageManager /// @@ -68,4 +68,5 @@ namespace PepperDash.Essentials.Core.PageManagers; TriList.BooleanInput[BackingPageJoin].BoolValue = false; TriList.BooleanInput[LeftSubpageJoin].BoolValue = false; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI PageManagers/SinglePageManager.cs b/src/PepperDash.Essentials.Core/UI PageManagers/SinglePageManager.cs index 9afa0555..3eb0cf80 100644 --- a/src/PepperDash.Essentials.Core/UI PageManagers/SinglePageManager.cs +++ b/src/PepperDash.Essentials.Core/UI PageManagers/SinglePageManager.cs @@ -2,8 +2,8 @@ using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Core.PageManagers; - +namespace PepperDash.Essentials.Core.PageManagers +{ /// /// Represents a SinglePageManager /// @@ -39,4 +39,5 @@ namespace PepperDash.Essentials.Core.PageManagers; { TriList.BooleanInput[BackingPageJoin].BoolValue = false; } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs index 83601cc4..fd9da802 100644 --- a/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs +++ b/src/PepperDash.Essentials.Core/UI/TouchpanelBase.cs @@ -4,167 +4,177 @@ using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; +using PepperDash.Core.Logging; using Serilog.Events; -namespace PepperDash.Essentials.Core.UI; - -public abstract class TouchpanelBase : EssentialsDevice, IHasBasicTriListWithSmartObject +namespace PepperDash.Essentials.Core.UI { - protected CrestronTouchpanelPropertiesConfig _config; - public BasicTriListWithSmartObject Panel { get; private set; } - /// - /// Constructor for use with device Factory. A touch panel device will be created based on the provided IP-ID and the - /// type of the panel. The SGD File path can be specified using the config property, or a default one located in the program directory if none - /// is provided. + /// Base class for Touchpanel devices /// - /// Essentials Device Key - /// Essentials Device Name - /// Touchpanel Type to build - /// Touchpanel Configuration - /// IP-ID to use for touch panel - protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config) - : base(key, name) + public abstract class TouchpanelBase : EssentialsDevice, IHasBasicTriListWithSmartObject { + /// + /// Gets or sets the configuration for the Crestron touchpanel. + /// + protected CrestronTouchpanelPropertiesConfig _config; + /// + /// Gets or sets the Panel + /// + public BasicTriListWithSmartObject Panel { get; private set; } - if (panel == null) + /// + /// Constructor for use with device Factory. A touch panel device will be created based on the provided IP-ID and the + /// type of the panel. The SGD File path can be specified using the config property, or a default one located in the program directory if none + /// is provided. + /// + /// Essentials Device Key + /// Essentials Device Name + /// Crestron Touchpanel Device + /// Touchpanel Configuration + protected TouchpanelBase(string key, string name, BasicTriListWithSmartObject panel, CrestronTouchpanelPropertiesConfig config) + : base(key, name) { - Debug.LogMessage(LogEventLevel.Information, this, "Panel is not valid. Touchpanel class WILL NOT work correctly"); - return; - } - Panel = panel; - - Panel.SigChange += Panel_SigChange; - - if (Panel is TswFt5ButtonSystem) - { - var tsw = Panel as TswFt5ButtonSystem; - tsw.ExtenderSystemReservedSigs.Use(); - tsw.ExtenderSystemReservedSigs.DeviceExtenderSigChange - += ExtenderSystemReservedSigs_DeviceExtenderSigChange; - - tsw.ButtonStateChange += Tsw_ButtonStateChange; - } - - _config = config; - - AddPreActivationAction(() => - { - if (Panel.Register() != eDeviceRegistrationUnRegistrationResponse.Success) - Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); - - // Give up cleanly if SGD is not present. - var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile; - if (!File.Exists(sgdName)) + if (panel == null) { - Debug.LogMessage(LogEventLevel.Information, this, "Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); - - sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile; - - if (!File.Exists(sgdName)) - { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); - return; - } + Debug.LogMessage(LogEventLevel.Information, this, "Panel is not valid. Touchpanel class WILL NOT work correctly"); + return; } - Panel.LoadSmartObjects(sgdName); - }); + Panel = panel; - AddPostActivationAction(() => - { - // Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event - var roomCombiner = DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) as IEssentialsRoomCombiner; + Panel.SigChange += Panel_SigChange; - if (roomCombiner != null) + if (Panel is TswFt5ButtonSystem) { - // Subscribe to the even - roomCombiner.RoomCombinationScenarioChanged += new EventHandler(roomCombiner_RoomCombinationScenarioChanged); + var tsw = Panel as TswFt5ButtonSystem; + tsw.ExtenderSystemReservedSigs.Use(); + tsw.ExtenderSystemReservedSigs.DeviceExtenderSigChange + += ExtenderSystemReservedSigs_DeviceExtenderSigChange; - // Connect to the initial roomKey - if (roomCombiner.CurrentScenario != null) + tsw.ButtonStateChange += Tsw_ButtonStateChange; + } + + _config = config; + + AddPreActivationAction(() => + { + // Give up cleanly if SGD is not present. + var sgdName = Global.FilePathPrefix + "sgd" + Global.DirectorySeparator + _config.SgdFile; + if (!File.Exists(sgdName)) { - // Use the current scenario - DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); + this.LogInformation("Smart object file '{0}' not present in User folder. Looking for embedded file", sgdName); + + sgdName = Global.ApplicationDirectoryPathPrefix + Global.DirectorySeparator + "SGD" + Global.DirectorySeparator + _config.SgdFile; + + if (!File.Exists(sgdName)) + { + this.LogWarning("Unable to find SGD file '{0}' in User sgd or application SGD folder. Exiting touchpanel load.", sgdName); + return; + } + } + + Panel.LoadSmartObjects(sgdName); + }); + + AddPostActivationAction(() => + { + // Check for IEssentialsRoomCombiner in DeviceManager and if found, subscribe to its event + + if (DeviceManager.AllDevices.FirstOrDefault((d) => d is IEssentialsRoomCombiner) is IEssentialsRoomCombiner roomCombiner) + { + // Subscribe to the even + roomCombiner.RoomCombinationScenarioChanged += new EventHandler(RoomCombiner_RoomCombinationScenarioChanged); + + // Connect to the initial roomKey + if (roomCombiner.CurrentScenario != null) + { + // Use the current scenario + DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); + } + else + { + // Current Scenario not yet set. Use default + SetupPanelDrivers(_config.DefaultRoomKey); + } } else { - // Current Scenario not yet set. Use default + // No room combiner, use the default key SetupPanelDrivers(_config.DefaultRoomKey); } - } - else + + var panelRegistrationResponse = Panel.Register(); + + if (panelRegistrationResponse != eDeviceRegistrationUnRegistrationResponse.Success) + this.LogInformation("WARNING: Registration failed. Continuing, but panel may not function: {0}", Panel.RegistrationFailureReason); + }); + } + + /// + /// Setup Panel operation + /// + /// Room Key for this panel + protected abstract void SetupPanelDrivers(string roomKey); + + /// + /// Event handler for System Extender Events + /// + /// + /// + protected abstract void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args); + + + /// + /// + /// + /// + /// + protected virtual void RoomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) + { + var roomCombiner = sender as IEssentialsRoomCombiner; + + DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); + } + + /// + /// Determines the room key to use based on the scenario + /// + /// + protected virtual void DetermineRoomKeyFromScenario(IRoomCombinationScenario scenario) + { + string newRoomKey = null; + + if (scenario.UiMap.ContainsKey(Key)) { - // No room combiner, use the default key - SetupPanelDrivers(_config.DefaultRoomKey); + newRoomKey = scenario.UiMap[Key]; + } + else if (scenario.UiMap.ContainsKey(_config.DefaultRoomKey)) + { + newRoomKey = scenario.UiMap[_config.DefaultRoomKey]; } - }); - } - /// - /// Setup Panel operation - /// - /// Room Key for this panel - protected abstract void SetupPanelDrivers(string roomKey); - - - /// - /// Event handler for System Extender Events - /// - /// - /// - protected abstract void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args); - - - /// - /// - /// - /// - /// - protected virtual void roomCombiner_RoomCombinationScenarioChanged(object sender, EventArgs e) - { - var roomCombiner = sender as IEssentialsRoomCombiner; - - DetermineRoomKeyFromScenario(roomCombiner.CurrentScenario); - } - - /// - /// Determines the room key to use based on the scenario - /// - /// - protected virtual void DetermineRoomKeyFromScenario(IRoomCombinationScenario scenario) - { - string newRoomKey = null; - - if (scenario.UiMap.ContainsKey(Key)) - { - newRoomKey = scenario.UiMap[Key]; - } - else if (scenario.UiMap.ContainsKey(_config.DefaultRoomKey)) - { - newRoomKey = scenario.UiMap[_config.DefaultRoomKey]; + SetupPanelDrivers(newRoomKey); } - SetupPanelDrivers(newRoomKey); - } + private void Panel_SigChange(object currentDevice, SigEventArgs args) + { + this.LogVerbose("Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); + var uo = args.Sig.UserObject; + if (uo is Action) + (uo as Action)(args.Sig.BoolValue); + else if (uo is Action) + (uo as Action)(args.Sig.UShortValue); + else if (uo is Action) + (uo as Action)(args.Sig.StringValue); + } - private void Panel_SigChange(object currentDevice, Crestron.SimplSharpPro.SigEventArgs args) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Sig change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); - var uo = args.Sig.UserObject; - if (uo is Action) - (uo as Action)(args.Sig.BoolValue); - else if (uo is Action) - (uo as Action)(args.Sig.UShortValue); - else if (uo is Action) - (uo as Action)(args.Sig.StringValue); - } - - private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) - { - var uo = args.Button.UserObject; - if (uo is Action) - (uo as Action)(args.Button.State == eButtonState.Pressed); + private void Tsw_ButtonStateChange(GenericBase device, ButtonEventArgs args) + { + var uo = args.Button.UserObject; + if (uo is Action) + (uo as Action)(args.Button.State == eButtonState.Pressed); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Utilities/ActionSequence.cs b/src/PepperDash.Essentials.Core/Utilities/ActionSequence.cs index aaea7b66..22c9297c 100644 --- a/src/PepperDash.Essentials.Core/Utilities/ActionSequence.cs +++ b/src/PepperDash.Essentials.Core/Utilities/ActionSequence.cs @@ -2,156 +2,186 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text; using Crestron.SimplSharp; using Crestron.SimplSharpPro.CrestronThread; using PepperDash.Core; +using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using Newtonsoft.Json; using Serilog.Events; -namespace PepperDash.Essentials.Core.Utilities; - -/// -/// A device that executes a sequence of actions with optional delays between actions -/// -[Description("A device that executes a sequence of actions with optional delays between actions")] -public class ActionSequence : EssentialsDevice +namespace PepperDash.Essentials.Core.Utilities { - private ActionSequencePropertiesConfig _propertiesConfig; - - private CrestronQueue _actionQueue; - - private Thread _worker; - - private bool _allowActionsToExecute; - - public ActionSequence(string key, DeviceConfig config) - : base(key, config.Name) + /// + /// A device that executes a sequence of actions with optional delays between actions + /// + [Description("A device that executes a sequence of actions with optional delays between actions")] + public class ActionSequence : EssentialsDevice { - var props = config.Properties.ToObject(); - _propertiesConfig = props; + private ActionSequencePropertiesConfig _propertiesConfig; - if (_propertiesConfig != null) + private CrestronQueue _actionQueue; + + private Thread _worker; + + private bool _allowActionsToExecute; + + /// + /// Constructor + /// + /// + /// + public ActionSequence(string key, DeviceConfig config) + : base(key, config.Name) { - if (_propertiesConfig.ActionSequence.Count > 0) + var props = config.Properties.ToObject(); + _propertiesConfig = props; + + if (_propertiesConfig != null) { - _actionQueue = new CrestronQueue(_propertiesConfig.ActionSequence.Count); + if (_propertiesConfig.ActionSequence.Count > 0) + { + _actionQueue = new CrestronQueue(_propertiesConfig.ActionSequence.Count); + } + } + } + + /// + /// StartSequence method + /// + public void StartSequence() + { + if (_worker !=null && _worker.ThreadState == Thread.eThreadStates.ThreadRunning) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Thread already running. Cannot Start Sequence"); + return; + } + + Debug.LogMessage(LogEventLevel.Debug, this, "Starting Action Sequence"); + _allowActionsToExecute = true; + AddActionsToQueue(); + _worker = new Thread(ProcessActions, null, Thread.eThreadStartOptions.Running); + } + + /// + /// StopSequence method + /// + public void StopSequence() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Action Sequence"); + _allowActionsToExecute = false; + _worker.Abort(); + } + + /// + /// Populates the queue from the configuration information + /// + private void AddActionsToQueue() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Adding {0} actions to queue", _propertiesConfig.ActionSequence.Count); + + for (int i = 0; i < _propertiesConfig.ActionSequence.Count; i++) + { + _actionQueue.Enqueue(_propertiesConfig.ActionSequence[i]); + } + } + + private object ProcessActions(object obj) + { + while (_allowActionsToExecute && _actionQueue.Count > 0) + { + SequencedDeviceActionWrapper action = null; + + action = _actionQueue.Dequeue(); + if (action == null) + break; + + // Delay before executing + if (action.DelayMs > 0) + Thread.Sleep(action.DelayMs); + + ExecuteAction(action); + } + + return null; + } + + private void ExecuteAction(DeviceActionWrapper action) + { + if (action == null) + return; + + try + { + DeviceJsonApi.DoDeviceAction(action); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Error Executing Action: {0}", e); } } } /// - /// Starts executing the sequenced actions + /// Represents a ActionSequencePropertiesConfig /// - public void StartSequence() + public class ActionSequencePropertiesConfig { - if (_worker !=null && _worker.ThreadState == Thread.eThreadStates.ThreadRunning) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Thread already running. Cannot Start Sequence"); - return; - } + /// + /// Gets or sets the ActionSequence + /// + [JsonProperty("actionSequence")] + public List ActionSequence { get; set; } - Debug.LogMessage(LogEventLevel.Debug, this, "Starting Action Sequence"); - _allowActionsToExecute = true; - AddActionsToQueue(); - _worker = new Thread(ProcessActions, null, Thread.eThreadStartOptions.Running); + /// + /// Constructor + /// + public ActionSequencePropertiesConfig() + { + ActionSequence = new List(); + } } /// - /// Stops executing the sequenced actions + /// Represents a SequencedDeviceActionWrapper /// - public void StopSequence() + public class SequencedDeviceActionWrapper : DeviceActionWrapper { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Action Sequence"); - _allowActionsToExecute = false; - _worker.Abort(); + /// + /// Gets or sets the DelayMs + /// + [JsonProperty("delayMs")] + public int DelayMs { get; set; } } /// - /// Populates the queue from the configuration information + /// Represents a ActionSequenceFactory /// - private void AddActionsToQueue() + public class ActionSequenceFactory : EssentialsDeviceFactory { - Debug.LogMessage(LogEventLevel.Debug, this, "Adding {0} actions to queue", _propertiesConfig.ActionSequence.Count); - - for (int i = 0; i < _propertiesConfig.ActionSequence.Count; i++) + /// + /// Constructor + /// + public ActionSequenceFactory() { - _actionQueue.Enqueue(_propertiesConfig.ActionSequence[i]); + TypeNames = new List() { "actionsequence" }; + } + + /// + /// BuildDevice method + /// + /// device config + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new ActionSequence Device"); + + return new ActionSequence(dc.Key, dc); } } - private object ProcessActions(object obj) - { - while (_allowActionsToExecute && _actionQueue.Count > 0) - { - SequencedDeviceActionWrapper action = null; - - action = _actionQueue.Dequeue(); - if (action == null) - break; - - // Delay before executing - if (action.DelayMs > 0) - Thread.Sleep(action.DelayMs); - - ExecuteAction(action); - } - - return null; - } - - private void ExecuteAction(DeviceActionWrapper action) - { - if (action == null) - return; - - try - { - DeviceJsonApi.DoDeviceAction(action); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error Executing Action: {0}", e); - } - } -} - -/// -/// Configuration Properties for ActionSequence -/// -public class ActionSequencePropertiesConfig -{ - [JsonProperty("actionSequence")] - public List ActionSequence { get; set; } - - public ActionSequencePropertiesConfig() - { - ActionSequence = new List(); - } -} - -public class SequencedDeviceActionWrapper : DeviceActionWrapper -{ - [JsonProperty("delayMs")] - public int DelayMs { get; set; } -} - -/// -/// Factory class -/// -public class ActionSequenceFactory : EssentialsDeviceFactory -{ - public ActionSequenceFactory() - { - TypeNames = new List() { "actionsequence" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new ActionSequence Device"); - - return new ActionSequence(dc.Key, dc); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/VideoStatus/VideoStatusOutputs.cs b/src/PepperDash.Essentials.Core/VideoStatus/VideoStatusOutputs.cs index 44fbf31d..b5151f79 100644 --- a/src/PepperDash.Essentials.Core/VideoStatus/VideoStatusOutputs.cs +++ b/src/PepperDash.Essentials.Core/VideoStatus/VideoStatusOutputs.cs @@ -6,8 +6,8 @@ using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DM; -namespace PepperDash.Essentials.Core; - +namespace PepperDash.Essentials.Core +{ /// /// Use this class to pass in values to RoutingInputPorts. Unused properties will have default /// funcs assigned to them. @@ -196,4 +196,5 @@ namespace PepperDash.Essentials.Core; // bool VideoSync { get; } //} - //public delegate void VideoStatusChangeHandler(IBasicVideoStatus device, eVideoStatusChangeType type); \ No newline at end of file + //public delegate void VideoStatusChangeHandler(IBasicVideoStatus device, eVideoStatusChangeType type); +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs index 9c16d47e..cfbaa1df 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApi.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; using PepperDash.Core; @@ -9,321 +8,315 @@ using PepperDash.Core.Web; using PepperDash.Essentials.Core.Web.RequestHandlers; using Serilog.Events; - -namespace PepperDash.Essentials.Core.Web; - -/// -/// Represents a EssentialsWebApi -/// -public class EssentialsWebApi : EssentialsDevice +namespace PepperDash.Essentials.Core.Web { - private readonly WebApiServer _server; - private readonly WebApiServer _debugServer; - - /// - /// http(s)://{ipaddress}/cws/{basePath} - /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} - /// - private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance - ? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber) - : "/api"; - /// - /// Gets or sets the BasePath + /// Represents a EssentialsWebApi /// - public string BasePath { get; private set; } - - /// - /// Tracks if CWS is registered - /// - public bool IsRegistered + public class EssentialsWebApi : EssentialsDevice { - get { return _server.IsRegistered; } - } + private readonly WebApiServer _server; - /// - /// Constructor - /// - /// - /// - public EssentialsWebApi(string key, string name) - : this(key, name, null) - { - } - - /// - /// Constructor - /// - /// - /// - /// - public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config) - : base(key, name) - { - Key = key; - - if (config == null) - BasePath = _defaultBasePath; - else - BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath; - - _server = new WebApiServer(Key, Name, BasePath); + private readonly WebApiServer _debugServer; - _debugServer = new WebApiServer($"{key}-debug-app", $"{name} Debug App", "/debug"); - _debugServer.SetFallbackHandler(new ServeDebugAppRequestHandler()); - SetupRoutes(); - } - private void SetupRoutes() - { - var routes = new List + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + private readonly string _defaultBasePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance + ? string.Format("/app{0:00}/api", InitialParametersClass.ApplicationNumber) + : "/api"; + + private const int DebugTrace = 0; + private const int DebugInfo = 1; + private const int DebugVerbose = 2; + + /// + /// Gets or sets the BasePath + /// + public string BasePath { get; private set; } + + /// + /// Tracks if CWS is registered + /// + public bool IsRegistered { - new HttpCwsRoute("login") - { - Name = "Root", - RouteHandler = new LoginRequestHandler() - }, - new HttpCwsRoute("versions") - { - Name = "ReportVersions", - RouteHandler = new ReportVersionsRequestHandler() - }, - new HttpCwsRoute("packageManifest") - { - Name = "GetPackageManifest", - RouteHandler = new GetPackageManifestRequestHandler() - }, - new HttpCwsRoute("appdebug") - { - Name = "AppDebug", - RouteHandler = new AppDebugRequestHandler() - }, - new HttpCwsRoute("devices") - { - Name = "DevList", - RouteHandler = new DevListRequestHandler() - }, - new HttpCwsRoute("deviceCommands/{deviceKey}") - { - Name = "DevJson", - RouteHandler = new DevJsonRequestHandler() - }, - new HttpCwsRoute("deviceProperties/{deviceKey}") - { - Name = "DevProps", - RouteHandler = new DevPropsRequestHandler() - }, - new HttpCwsRoute("deviceMethods/{deviceKey}") - { - Name = "DevMethods", - RouteHandler = new DevMethodsRequestHandler() - }, - new HttpCwsRoute("deviceFeedbacks/{deviceKey}") - { - Name = "GetFeedbacksForDeviceKey", - RouteHandler = new GetFeedbacksForDeviceRequestHandler() - }, - new HttpCwsRoute("deviceStreamDebug") - { - Name = "SetDeviceStreamDebug", - RouteHandler = new SetDeviceStreamDebugRequestHandler() - }, - new HttpCwsRoute("disableAllStreamDebug") - { - Name = "DisableAllStreamDebug", - RouteHandler = new DisableAllStreamDebugRequestHandler() - }, - new HttpCwsRoute("config") - { - Name = "ShowConfig", - RouteHandler = new ShowConfigRequestHandler() - }, - new HttpCwsRoute("types") - { - Name = "GetTypes", - RouteHandler = new GetTypesRequestHandler() - }, - new HttpCwsRoute("types/{filter}") - { - Name = "GetTypesByFilter", - RouteHandler = new GetTypesByFilterRequestHandler() - }, - new HttpCwsRoute("joinMap/{bridgeKey}") - { - Name = "GetJoinMapsForBridgeKey", - RouteHandler = new GetJoinMapForBridgeKeyRequestHandler() - }, - new HttpCwsRoute("joinMap/{bridgeKey}/{deviceKey}") - { - Name = "GetJoinMapsForDeviceKey", - RouteHandler = new GetJoinMapForDeviceKeyRequestHandler() - }, - new HttpCwsRoute("debugSession") - { - Name = "DebugSession", - RouteHandler = new DebugSessionRequestHandler() - }, - new HttpCwsRoute("doNotLoadConfigOnNextBoot") - { - Name = "DoNotLoadConfigOnNextBoot", - RouteHandler = new DoNotLoadConfigOnNextBootRequestHandler() - }, - new HttpCwsRoute("restartProgram") - { - Name = "Restart Program", - RouteHandler = new RestartProgramRequestHandler() - }, - new HttpCwsRoute("loadConfig") - { - Name = "Load Config", - RouteHandler = new LoadConfigRequestHandler() - }, - new HttpCwsRoute("tielines") - { - Name = "Get TieLines", - RouteHandler = new GetTieLinesRequestHandler() - }, - new HttpCwsRoute("device/{deviceKey}/routingPorts") - { - Name = "Get Routing Ports for a device", - RouteHandler = new GetRoutingPortsHandler() - }, - new HttpCwsRoute("routingDevicesAndTieLines") - { - Name = "Get Routing Devices and TieLines", - RouteHandler = new GetRoutingDevicesAndTieLinesHandler() - }, - new HttpCwsRoute("routingFeedbackSession") - { - Name = "Routing Feedback WebSocket Session", - RouteHandler = new RoutingFeedbackSessionRequestHandler() - }, - new HttpCwsRoute("initializationExceptions") - { - Name = "Get Initialization Exceptions", - RouteHandler = new GetInitializationExceptionsRequestHandler() - } - }; - - AddRoute(routes); - } - - /// - /// Add a single route to the API. MUST be done during the activation phase - /// - /// - public void AddRoute(HttpCwsRoute route) - { - _server.AddRoute(route); - } - - /// - /// Add a collection of routes to the API. MUST be done during the activation phase - /// - /// - public void AddRoute(List routes) - { - foreach (var route in routes) - { - AddRoute(route); + get { return _server.IsRegistered; } } - } - /// - /// Initializes the CWS class - /// - protected override void Initialize() - { - AddRoute(new HttpCwsRoute("apiPaths") + /// + /// Constructor + /// + /// + /// + public EssentialsWebApi(string key, string name) + : this(key, name, null) { - Name = "GetPaths", - RouteHandler = new GetRoutesHandler(_server.GetRouteCollection(), BasePath) - }); + } - // If running on an appliance - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + /// + /// Constructor + /// + /// + /// + /// + public EssentialsWebApi(string key, string name, EssentialsWebApiPropertiesConfig config) + : base(key, name) { - /* - WEBSERVER [ON | OFF | TIMEOUT | MAXSESSIONSPERUSER ] - */ - var response = string.Empty; - CrestronConsole.SendControlSystemCommand("webserver", ref response); - if (response.Contains("OFF")) return; + Key = key; - var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4); - Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series"); + if (config == null) + BasePath = _defaultBasePath; + else + BasePath = string.IsNullOrEmpty(config.BasePath) ? _defaultBasePath : config.BasePath; + + _server = new WebApiServer(Key, Name, BasePath); + + _debugServer = new WebApiServer($"{key}-debug-app", $"{name} Debug App", "/debug"); + _debugServer.SetFallbackHandler(new ServeDebugAppRequestHandler()); + + SetupRoutes(); + } + + private void SetupRoutes() + { + var routes = new List + { + new HttpCwsRoute("login") + { + Name = "Root", + RouteHandler = new LoginRequestHandler() + }, + new HttpCwsRoute("versions") + { + Name = "ReportVersions", + RouteHandler = new ReportVersionsRequestHandler() + }, + new HttpCwsRoute("packageManifest") + { + Name = "GetPackageManifest", + RouteHandler = new GetPackageManifestRequestHandler() + }, + new HttpCwsRoute("appdebug") + { + Name = "AppDebug", + RouteHandler = new AppDebugRequestHandler() + }, + new HttpCwsRoute("devices") + { + Name = "DevList", + RouteHandler = new DevListRequestHandler() + }, + new HttpCwsRoute("deviceCommands/{deviceKey}") + { + Name = "DevJson", + RouteHandler = new DevJsonRequestHandler() + }, + new HttpCwsRoute("deviceProperties/{deviceKey}") + { + Name = "DevProps", + RouteHandler = new DevPropsRequestHandler() + }, + new HttpCwsRoute("deviceMethods/{deviceKey}") + { + Name = "DevMethods", + RouteHandler = new DevMethodsRequestHandler() + }, + new HttpCwsRoute("deviceFeedbacks/{deviceKey}") + { + Name = "GetFeedbacksForDeviceKey", + RouteHandler = new GetFeedbacksForDeviceRequestHandler() + }, + new HttpCwsRoute("deviceStreamDebug") + { + Name = "SetDeviceStreamDebug", + RouteHandler = new SetDeviceStreamDebugRequestHandler() + }, + new HttpCwsRoute("disableAllStreamDebug") + { + Name = "DisableAllStreamDebug", + RouteHandler = new DisableAllStreamDebugRequestHandler() + }, + new HttpCwsRoute("config") + { + Name = "ShowConfig", + RouteHandler = new ShowConfigRequestHandler() + }, + new HttpCwsRoute("types") + { + Name = "GetTypes", + RouteHandler = new GetTypesRequestHandler() + }, + new HttpCwsRoute("types/{filter}") + { + Name = "GetTypesByFilter", + RouteHandler = new GetTypesByFilterRequestHandler() + }, + new HttpCwsRoute("joinMap/{bridgeKey}") + { + Name = "GetJoinMapsForBridgeKey", + RouteHandler = new GetJoinMapForBridgeKeyRequestHandler() + }, + new HttpCwsRoute("joinMap/{bridgeKey}/{deviceKey}") + { + Name = "GetJoinMapsForDeviceKey", + RouteHandler = new GetJoinMapForDeviceKeyRequestHandler() + }, + new HttpCwsRoute("debugSession") + { + Name = "DebugSession", + RouteHandler = new DebugSessionRequestHandler() + }, + new HttpCwsRoute("doNotLoadConfigOnNextBoot") + { + Name = "DoNotLoadConfigOnNextBoot", + RouteHandler = new DoNotLoadConfigOnNextBootRequestHandler() + }, + new HttpCwsRoute("restartProgram") + { + Name = "Restart Program", + RouteHandler = new RestartProgramRequestHandler() + }, + new HttpCwsRoute("loadConfig") + { + Name = "Load Config", + RouteHandler = new LoadConfigRequestHandler() + }, + new HttpCwsRoute("tielines") + { + Name = "Get TieLines", + RouteHandler = new GetTieLinesRequestHandler() + }, + new HttpCwsRoute("device/{deviceKey}/routingPorts") + { + Name = "Get Routing Ports for a device", + RouteHandler = new GetRoutingPortsHandler() + }, + new HttpCwsRoute("routingDevicesAndTieLines") + { + Name = "Get Routing Devices and TieLines", + RouteHandler = new GetRoutingDevicesAndTieLinesHandler() + }, + }; + + AddRoute(routes); + } + + /// + /// Add a single route to the API. MUST be done during the activation phase + /// + /// + /// + /// AddRoute method + /// + public void AddRoute(HttpCwsRoute route) + { + _server.AddRoute(route); + } + + /// + /// Add a collection of routes to the API. MUST be done during the activation phase + /// + /// + public void AddRoute(List routes) + { + foreach (var route in routes) + { + AddRoute(route); + } + } + + /// + /// Initialize method + /// + /// + public override void Initialize() + { + AddRoute(new HttpCwsRoute("apiPaths") + { + Name = "GetPaths", + RouteHandler = new GetRoutesHandler(_server.GetRouteCollection(), BasePath) + }); + + // If running on an appliance + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + /* + WEBSERVER [ON | OFF | TIMEOUT | MAXSESSIONSPERUSER ] + */ + var response = string.Empty; + CrestronConsole.SendControlSystemCommand("webserver", ref response); + if (response.Contains("OFF")) return; + + var is4Series = eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4); + Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on {0} Appliance", is4Series ? "4-series" : "3-series"); + + _server.Start(); + _debugServer.Start(); + + GetPaths(); + + return; + } + + // Automatically start CWS when running on a server (Linux OS, Virtual Control) + Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server"); _server.Start(); _debugServer.Start(); - PrintPaths(); - - return; + GetPaths(); } - // Automatically start CWS when running on a server (Linux OS, Virtual Control) - Debug.LogMessage(LogEventLevel.Verbose, "Starting Essentials Web API on Virtual Control Server"); - - _server.Start(); - _debugServer.Start(); - - PrintPaths(); - } - - /// - /// Print the available paths - /// - /// - /// http(s)://{ipaddress}/cws/{basePath} - /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} - /// - /// - /// GetPaths method - /// - public void PrintPaths() - { - Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); - - var currentIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - - var hostname = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); - - var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server - ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}" - : $"https://{currentIp}/cws{BasePath}"; - - Debug.LogMessage(LogEventLevel.Information, this, "Server: {path:l}", path); - - var routeCollection = _server.GetRouteCollection(); - if (routeCollection == null) + /// + /// Print the available paths + /// + /// + /// http(s)://{ipaddress}/cws/{basePath} + /// http(s)://{ipaddress}/VirtualControl/Rooms/{roomId}/cws/{basePath} + /// + /// + /// GetPaths method + /// + public void GetPaths() { - Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null"); - return; + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); + + var currentIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + + var hostname = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + + var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server + ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{BasePath}" + : $"https://{currentIp}/cws{BasePath}"; + + Debug.LogMessage(LogEventLevel.Information, this, "Server:{path:l}", path); + + var routeCollection = _server.GetRouteCollection(); + if (routeCollection == null) + { + Debug.LogMessage(LogEventLevel.Information, this, "Server route collection is null"); + return; + } + Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:"); + foreach (var route in routeCollection) + { + Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url); + } + Debug.LogInformation(this, "Web API initialized and ready to accept requests"); + + Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); + + var debugAppUrl = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server + ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws/debug" + : $"https://{currentIp}/cws/debug"; + + Debug.LogMessage(LogEventLevel.Information, this, "Developer Tools Web App available at: {debugAppUrl:l}", debugAppUrl); } - Debug.LogMessage(LogEventLevel.Information, this, "Configured Routes:"); - foreach (var route in routeCollection) - { - Debug.LogMessage(LogEventLevel.Information, this, "{routeName:l}: {routePath:l}/{routeUrl:l}", route.Name, path, route.Url); - } - - var debugPath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server - ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws/debug" - : $"https://{currentIp}/cws/debug"; - Debug.LogMessage(LogEventLevel.Information, this, "Debug App: {debugPath:l}", debugPath); - - Debug.LogInformation(this, "Web API initialized and ready to accept requests"); - - Debug.LogMessage(LogEventLevel.Information, this, new string('-', 50)); - - var debugAppUrl = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server -? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws/debug" -: $"https://{currentIp}/cws/debug"; - - Debug.LogMessage(LogEventLevel.Information, this, "Developer Tools Web App available at: {debugAppUrl:l}", debugAppUrl); } -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiFactory.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiFactory.cs index 4a8f25e0..16abd349 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiFactory.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiFactory.cs @@ -3,33 +3,34 @@ using PepperDash.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Core.Web; - -/// -/// Factory for creating EssentialsWebApi devices -/// -public class EssentialsWebApiFactory : EssentialsDeviceFactory +namespace PepperDash.Essentials.Core.Web { - /// - /// Constructor - /// - public EssentialsWebApiFactory() + /// + /// Represents a EssentialsWebApiFactory + /// + public class EssentialsWebApiFactory : EssentialsDeviceFactory { - TypeNames = new List { "EssentialsWebApi" }; - } + /// + /// Constructor + /// + public EssentialsWebApiFactory() + { + TypeNames = new List { "EssentialsWebApi" }; + } - /// - /// BuildDevice method - /// - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Essentials Web API Server"); + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Essentials Web API Server"); - var props = dc.Properties.ToObject(); - if (props != null) return new EssentialsWebApi(dc.Key, dc.Name, props); + var props = dc.Properties.ToObject(); + if (props != null) return new EssentialsWebApi(dc.Key, dc.Name, props); - Debug.LogMessage(LogEventLevel.Debug, "Factory failed to create new Essentials Web API Server"); - return null; + Debug.LogMessage(LogEventLevel.Debug, "Factory failed to create new Essentials Web API Server"); + return null; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs index bb7e8502..c966ea59 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiHelpers.cs @@ -1,118 +1,123 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Crestron.SimplSharp.WebScripting; using PepperDash.Core; -namespace PepperDash.Essentials.Core.Web; - -/// -/// EssentialsWebApiHelpers class -/// -public static class EssentialsWebApiHelpers +namespace PepperDash.Essentials.Core.Web { /// - /// GetRequestBody method + /// EssentialsWebApiHelpers class /// - public static string GetRequestBody(this HttpCwsRequest request) + public static class EssentialsWebApiHelpers { - var bytes = new byte[request.ContentLength]; - - request.InputStream.Read(bytes, 0, request.ContentLength); - - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - /// - /// MapToAssemblyObject method - /// - public static object MapToAssemblyObject(LoadedAssembly assembly) - { - return new + /// + /// GetRequestBody method + /// + public static string GetRequestBody(this HttpCwsRequest request) { - assembly.Name, - assembly.Version - }; - } + var bytes = new byte[request.ContentLength]; - /// - /// MapToDeviceListObject method - /// - public static object MapToDeviceListObject(IKeyed device) - { - var interfaces = device.GetType() - .GetInterfaces() - .Select(i => i.Name) - .ToList(); + request.InputStream.Read(bytes, 0, request.ContentLength); - return new + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + /// + /// MapToAssemblyObject method + /// + public static object MapToAssemblyObject(LoadedAssembly assembly) { - device.Key, - Name = (device is IKeyName) - ? (device as IKeyName).Name - : "---", - Interfaces = interfaces - }; - } + return new + { + assembly.Name, + assembly.Version + }; + } - /// - /// MapJoinToObject method - /// - public static object MapJoinToObject(string key, JoinMapBaseAdvanced join) - { - var kp = new KeyValuePair(key, join); - return MapJoinToObject(kp); - } - /// - /// MapJoinToObject method - /// - public static object MapJoinToObject(KeyValuePair join) - { - return new + /// + /// MapToDeviceListObject method + /// + public static object MapToDeviceListObject(IKeyed device) { - DeviceKey = join.Key, - Joins = join.Value.Joins.Select(j => MapJoinDataCompleteToObject(j)) - }; - } + var interfaces = device.GetType() + .GetInterfaces() + .Select(i => i.Name) + .ToList(); - /// - /// MapJoinDataCompleteToObject method - /// - public static object MapJoinDataCompleteToObject(KeyValuePair joinData) - { - return new + return new + { + device.Key, + Name = (device is IKeyName) + ? (device as IKeyName).Name + : "---", + Interfaces = interfaces + }; + } + + /// + /// MapJoinToObject method + /// + public static object MapJoinToObject(string key, JoinMapBaseAdvanced join) { - Signal = joinData.Key, - Description = joinData.Value.Metadata.Description, - JoinNumber = joinData.Value.JoinNumber, - JoinSpan = joinData.Value.JoinSpan, - JoinType = joinData.Value.Metadata.JoinType.ToString(), - JoinCapabilities = joinData.Value.Metadata.JoinCapabilities.ToString() - }; - } + var kp = new KeyValuePair(key, join); - /// - /// MapDeviceTypeToObject method - /// - public static object MapDeviceTypeToObject(string key, DeviceFactoryWrapper device) - { - var kp = new KeyValuePair(key, device); + return MapJoinToObject(kp); + } - return MapDeviceTypeToObject(kp); - } - - /// - /// MapDeviceTypeToObject method - /// - public static object MapDeviceTypeToObject(KeyValuePair device) - { - return new + /// + /// MapJoinToObject method + /// + public static object MapJoinToObject(KeyValuePair join) { - Type = device.Key, - Description = device.Value.Description, - CType = device.Value.Type == null ? "---" : device.Value.Type.ToString() - }; - } + return new + { + DeviceKey = join.Key, + Joins = join.Value.Joins.Select(j => MapJoinDataCompleteToObject(j)) + }; + } + + /// + /// MapJoinDataCompleteToObject method + /// + public static object MapJoinDataCompleteToObject(KeyValuePair joinData) + { + return new + { + Signal = joinData.Key, + Description = joinData.Value.Metadata.Description, + JoinNumber = joinData.Value.JoinNumber, + JoinSpan = joinData.Value.JoinSpan, + JoinType = joinData.Value.Metadata.JoinType.ToString(), + JoinCapabilities = joinData.Value.Metadata.JoinCapabilities.ToString() + }; + } + + /// + /// MapDeviceTypeToObject method + /// + public static object MapDeviceTypeToObject(string key, DeviceFactoryWrapper device) + { + var kp = new KeyValuePair(key, device); + + return MapDeviceTypeToObject(kp); + } + + /// + /// MapDeviceTypeToObject method + /// + public static object MapDeviceTypeToObject(KeyValuePair device) + { + return new + { + Type = device.Key, + Description = device.Value.Description, + CType = device.Value.Type == null ? "---": device.Value.Type.ToString() + }; + } + + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiPropertiesConfig.cs b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiPropertiesConfig.cs index 63e68544..9c00fd93 100644 --- a/src/PepperDash.Essentials.Core/Web/EssentialsWebApiPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Core/Web/EssentialsWebApiPropertiesConfig.cs @@ -1,15 +1,16 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials.Core.Web; - -/// -/// Represents a EssentialsWebApiPropertiesConfig -/// -public class EssentialsWebApiPropertiesConfig +namespace PepperDash.Essentials.Core.Web { /// - /// Gets or sets the BasePath + /// Represents a EssentialsWebApiPropertiesConfig /// - [JsonProperty("basePath")] - public string BasePath { get; set; } + public class EssentialsWebApiPropertiesConfig + { + /// + /// Gets or sets the BasePath + /// + [JsonProperty("basePath")] + public string BasePath { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs index 2bc8ce09..3d3302cc 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/AppDebugRequestHandler.cs @@ -2,92 +2,94 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; +using System; using Serilog.Events; using Newtonsoft.Json.Converters; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Represents a AppDebugRequestHandler -/// -public class AppDebugRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public AppDebugRequestHandler() - : base(true) + /// + /// Represents a AppDebugRequestHandler + /// + public class AppDebugRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var appDebug = new AppDebug { MinimumLevel = Debug.WebsocketMinimumLogLevel }; - - var body = JsonConvert.SerializeObject(appDebug, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(body, false); - context.Response.End(); - } - - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - if (context.Request.ContentLength < 0) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public AppDebugRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - var data = context.Request.GetRequestBody(); - if (string.IsNullOrEmpty(data)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var appDebug = new AppDebug { MinimumLevel = Debug.WebsocketMinimumLogLevel }; + + var body = JsonConvert.SerializeObject(appDebug, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(body, false); context.Response.End(); - - return; } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - var appDebug = new AppDebug(); - var requestBody = JsonConvert.DeserializeObject(data); + return; + } - Debug.SetWebSocketMinimumDebugLevel(requestBody.MinimumLevel); + var data = context.Request.GetRequestBody(); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - appDebug.MinimumLevel = Debug.WebsocketMinimumLogLevel; - var responseBody = JsonConvert.SerializeObject(appDebug, Formatting.Indented); + return; + } - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(responseBody, false); - context.Response.End(); + var appDebug = new AppDebug(); + var requestBody = JsonConvert.DeserializeObject(data); + + Debug.SetWebSocketMinimumDebugLevel(requestBody.MinimumLevel); + + appDebug.MinimumLevel = Debug.WebsocketMinimumLogLevel; + var responseBody = JsonConvert.SerializeObject(appDebug, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(responseBody, false); + context.Response.End(); + } } -} -/// -/// Represents a AppDebug -/// -public class AppDebug -{ /// - /// Gets or sets the MinimumLevel + /// Represents a AppDebug /// - [JsonProperty("minimumLevel", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(StringEnumConverter))] - public LogEventLevel MinimumLevel { get; set; } + public class AppDebug + { + /// + /// Gets or sets the MinimumLevel + /// + [JsonProperty("minimumLevel", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public LogEventLevel MinimumLevel { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs index ea9c5b82..59c662dc 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugSessionRequestHandler.cs @@ -1,204 +1,154 @@ using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; +using Crestron.SimplSharpPro.EthernetCommunication; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; using Serilog.Events; using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Represents a DebugSessionRequestHandler -/// -public class DebugSessionRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - private CTimer _portForwardTimeoutTimer; - private readonly object _timerLock = new object(); + /// + /// Represents a DebugSessionRequestHandler + /// + public class DebugSessionRequestHandler : WebApiBaseRequestHandler + { + private CTimer _portForwardTimeoutTimer; + private readonly object _timerLock = new object(); - /// - /// Constructor - /// - public DebugSessionRequestHandler() - : base(true) - { - } - /// - /// Gets details for a debug session - /// - /// - protected override void HandleGet(Crestron.SimplSharp.WebScripting.HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + public DebugSessionRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - try + /// + /// Gets details for a debug session + /// + /// + protected override void HandleGet(Crestron.SimplSharp.WebScripting.HttpCwsContext context) { - var ip = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - - var port = 0; - string csIp = null; - - if (!Debug.WebsocketSink.IsRunning) + var routeData = context.Request.RouteData; + if (routeData == null) { - Debug.LogMessage(LogEventLevel.Information, "Starting WS Server"); - // Generate a random port within a specified range - port = new Random().Next(65435, 65535); - // Start the WS Server - Debug.WebsocketSink.StartServerAndSetPort(port); - Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); - } - else - { - port = Debug.WebsocketSink.Port; + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; } - // Always ensure port forwarding is active — it may have been removed by timeout + try + { + var ip = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + + var port = 0; + string csIp = null; + + if (!Debug.WebsocketSink.IsRunning) + { + Debug.LogMessage(LogEventLevel.Information, "Starting WS Server"); + // Generate a random port within a specified range + port = new Random().Next(65435, 65535); + // Start the WS Server + Debug.WebsocketSink.StartServerAndSetPort(port); + Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose); + } + + // Attempt to get the CS LAN IP and forward the port + try + { + var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( + EthernetAdapterType.EthernetCSAdapter); + csIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); + + if (port > 0) + { + var result = CrestronEthernetHelper.AddPortForwarding( + (ushort)port, (ushort)port, csIp, + CrestronEthernetHelper.ePortMapTransport.TCP); + + if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) + { + Debug.LogMessage(LogEventLevel.Warning, "Error adding port forwarding for debug websocket: {0}", result); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port); + StartPortForwardTimeout(port, csIp); + } + } + } + catch (ArgumentException) + { + Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message); + } + + var url = Debug.WebsocketSink.Url; + + var data = new + { + url = Debug.WebsocketSink.Url, + fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null + }; + + Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url); + Debug.LogMessage(LogEventLevel.Information, "Fallback Debug Session URL: {0}", data.fallbackUrl); + + // Return the port number with the full url of the WS Server + var res = JsonConvert.SerializeObject(data); + + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(res, false); + context.Response.End(); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, "Error: {0}", e); + } + } + + /// + /// Stops a debug session + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + CancelPortForwardTimeout(); + + var port = Debug.WebsocketSink.Port; + + Debug.WebsocketSink.StopServer(); + + // Remove the port forwarding entry try { var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( EthernetAdapterType.EthernetCSAdapter); - csIp = CrestronEthernetHelper.GetEthernetParameter( + var csIp = CrestronEthernetHelper.GetEthernetParameter( CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - if (port > 0) + if (port <= 0) { - var result = CrestronEthernetHelper.AddPortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error forwarding port {0} to CS LAN: {1}", port, result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for debug websocket", port); - StartPortForwardTimeout(port, csIp); - } + Debug.LogMessage(LogEventLevel.Debug, "Debug websocket port is not set; skipping port forwarding removal"); } - } - catch (ArgumentException) - { - Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding debug websocket port to CS LAN: {0}", ex.Message); - } - - if (!Debug.WebsocketSink.IsRunning) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.Write(JsonConvert.SerializeObject(new { error = "Failed to start WebSocket debug server. Check logs for details." }), false); - context.Response.End(); - return; - } - - var url = Debug.WebsocketSink.Url; - - var data = new - { - url = Debug.WebsocketSink.Url, - fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null - }; - - Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url); - Debug.LogMessage(LogEventLevel.Information, "Fallback Debug Session URL: {0}", data.fallbackUrl); - - // Return the port number with the full url of the WS Server - var res = JsonConvert.SerializeObject(data); - - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(res, false); - context.Response.End(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Error: {0}", e); - } - } - - /// - /// Stops a debug session - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - CancelPortForwardTimeout(); - - var port = Debug.WebsocketSink.Port; - - Task.Run(() => Debug.WebsocketSink.StopServer()); - - // Remove port forwarding if CS LAN exists - try - { - var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( - EthernetAdapterType.EthernetCSAdapter); - var csIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - - var result = CrestronEthernetHelper.RemovePortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for port {0}: {1}", port, result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port); - } - } - catch (ArgumentException) - { - // No CS LAN adapter - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding: {0}", ex.Message); - } - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.End(); - - Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped"); - } - - private void StartPortForwardTimeout(int port, string csIp) - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = new CTimer(_ => - { - if (Debug.WebsocketSink.HasActiveConnections) - { - Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward"); - StartPortForwardTimeout(port, csIp); - return; - } - - Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within timeout; removing port forward for port {0}", port); - - try + else { var result = CrestronEthernetHelper.RemovePortForwarding( (ushort)port, (ushort)port, csIp, @@ -206,30 +156,79 @@ public class DebugSessionRequestHandler : WebApiBaseRequestHandler if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result); + Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for debug websocket: {0}", result); } else { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port); + Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed", port); } } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message); - } - }, 120000); - } - } + } + catch (ArgumentException) + { + Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding removal"); + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Warning, "Error removing debug websocket port forwarding: {0}", ex.Message); + } - /// - /// Cancels the port forward timeout timer if a session is being explicitly stopped. - /// - private void CancelPortForwardTimeout() - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = null; + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + + Debug.LogMessage(LogEventLevel.Information, "Websocket Debug Session Stopped"); } + + private void StartPortForwardTimeout(int port, string csIp) + { + lock (_timerLock) + { + _portForwardTimeoutTimer?.Dispose(); + _portForwardTimeoutTimer = new CTimer(_ => + { + if (Debug.WebsocketSink.HasActiveConnections) + { + Debug.LogMessage(LogEventLevel.Debug, "Debug websocket has active connections; keeping port forward"); + return; + } + + Debug.LogMessage(LogEventLevel.Information, "No debug websocket connection within 30 seconds; removing port forward for port {0}", port); + + try + { + var result = CrestronEthernetHelper.RemovePortForwarding( + (ushort)port, (ushort)port, csIp, + CrestronEthernetHelper.ePortMapTransport.TCP); + + if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) + { + Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Port forwarding for port {0} removed due to timeout", port); + } + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message); + } + }, 30000); + } + } + + /// + /// Cancels the port forward timeout timer if a session is being explicitly stopped. + /// + private void CancelPortForwardTimeout() + { + lock (_timerLock) + { + _portForwardTimeoutTimer?.Dispose(); + _portForwardTimeoutTimer = null; + } + } + } } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugWebsocketSink.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugWebsocketSink.cs deleted file mode 100644 index 34d8b446..00000000 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DebugWebsocketSink.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DebugWebsocketSink -{ - private bool _isRunning; - private string _url; - - public bool IsRunning => _isRunning; - public string Url => _url; - - public void StartServerAndSetPort(int port) - { - try - { - _url = $"ws://localhost:{port}"; - _isRunning = true; - // Implement actual server startup logic here - } - catch (Exception ex) - { - _isRunning = false; - throw new Exception($"Failed to start debug websocket server: {ex.Message}"); - } - } - - public void StopServer() - { - try - { - // Implement actual server shutdown logic here - _isRunning = false; - _url = null; - } - catch (Exception ex) - { - throw new Exception($"Failed to stop debug websocket server: {ex.Message}"); - } - } -} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DefaultRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DefaultRequestHandler.cs index 24ad97bc..c0fec253 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DefaultRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DefaultRequestHandler.cs @@ -1,8 +1,11 @@ using Crestron.SimplSharp.WebScripting; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + /// + /// Represents a DefaultRequestHandler + /// public class DefaultRequestHandler : WebApiBaseRequestHandler { /// @@ -114,4 +117,5 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers; context.Response.StatusDescription = "I'm a teapot"; context.Response.End(); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs index f0cefb1c..51b261fe 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevJsonRequestHandler.cs @@ -5,8 +5,11 @@ using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; using Serilog.Events; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - +namespace PepperDash.Essentials.Core.Web.RequestHandlers +{ + /// + /// Represents a DevJsonRequestHandler + /// public class DevJsonRequestHandler : WebApiBaseRequestHandler { /// @@ -26,25 +29,25 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers; /// protected override void HandlePost(HttpCwsContext context) { - var routeData = context.Request.RouteData; + var routeData = context.Request.RouteData; - if(routeData == null) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); + if(routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - return; - } + return; + } - if(!routeData.Values.TryGetValue("deviceKey", out var deviceKey)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); + if(!routeData.Values.TryGetValue("deviceKey", out var deviceKey)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - return; - } + return; + } if (context.Request.ContentLength < 0) { @@ -68,11 +71,11 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers; try { - var daw = new DeviceActionWrapper { DeviceKey = (string) deviceKey}; + var daw = new DeviceActionWrapper { DeviceKey = (string) deviceKey}; - JsonConvert.PopulateObject(data, daw); + JsonConvert.PopulateObject(data, daw); - Debug.LogMessage(LogEventLevel.Verbose, "Device Action Wrapper: {@wrapper}", null, daw); + Debug.LogMessage(LogEventLevel.Verbose, "Device Action Wrapper: {@wrapper}", null, daw); DeviceJsonApi.DoDeviceAction(daw); @@ -86,8 +89,9 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers; context.Response.StatusCode = 400; context.Response.StatusDescription = "Bad Request"; - context.Response.Write(JsonConvert.SerializeObject(new { error = ex.Message }), false); + context.Response.Write(JsonConvert.SerializeObject(new { error = ex.Message }), false); context.Response.End(); } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs index 420cd26c..31bfd6f9 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevListRequestHandler.cs @@ -3,48 +3,52 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DevListRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public DevListRequestHandler() - : base(true) + /// + /// Represents a DevListRequestHandler + /// + public class DevListRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var allDevices = DeviceManager.AllDevices; - if (allDevices == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public DevListRequestHandler() + : base(true) { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; } - allDevices.Sort((a, b) => string.Compare(a.Key, b.Key, System.StringComparison.Ordinal)); + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var allDevices = DeviceManager.AllDevices; + if (allDevices == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); - var deviceList = allDevices.Select(d => EssentialsWebApiHelpers.MapToDeviceListObject(d)).ToList(); + return; + } - var js = JsonConvert.SerializeObject(deviceList, Formatting.Indented); + allDevices.Sort((a, b) => string.Compare(a.Key, b.Key, System.StringComparison.Ordinal)); - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); + var deviceList = allDevices.Select(d => EssentialsWebApiHelpers.MapToDeviceListObject(d)).ToList(); + + var js = JsonConvert.SerializeObject(deviceList, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs index f3bb5c1f..004ce5ed 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevMethodsRequestHandler.cs @@ -1,76 +1,81 @@ using System.Text; using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DevMethodsRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public DevMethodsRequestHandler() - : base(true) + /// + /// Represents a DevMethodsRequestHandler + /// + public class DevMethodsRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Getting DevMethods: {@routeData}", routeData); - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public DevMethodsRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - object deviceObj; - if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Getting DevMethods: {@routeData}", routeData); + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()); + + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Device Not Found"; + context.Response.End(); + + return; + } + + var deviceMethods = DeviceJsonApi.GetMethods(device.Key); + if (deviceMethods == null || deviceMethods.ToLower().Contains("no device")) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(deviceMethods, false); context.Response.End(); - - return; } - - var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()); - - if (device == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Device Not Found"; - context.Response.End(); - - return; - } - - var deviceMethods = DeviceJsonApi.GetMethods(device.Key); - if (deviceMethods == null || deviceMethods.ToLower().Contains("no device")) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(deviceMethods, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevPropsRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevPropsRequestHandler.cs index 1d51b204..2117619b 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevPropsRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DevPropsRequestHandler.cs @@ -1,74 +1,79 @@ using System.Text; using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DevPropsRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public DevPropsRequestHandler() - : base(true) - { - } + /// + /// Represents a DevPropsRequestHandler + /// + public class DevPropsRequestHandler : WebApiBaseRequestHandler + { + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public DevPropsRequestHandler() + : base(true) + { + } - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()); + + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Device Not Found"; + context.Response.End(); + + return; + } + + var deviceProperties = DeviceJsonApi.GetProperties(device.Key); + if (deviceProperties == null || deviceProperties.ToLower().Contains("no device")) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(deviceProperties, false); context.Response.End(); - - return; } - - object deviceObj; - if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()); - - if (device == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Device Not Found"; - context.Response.End(); - - return; - } - - var deviceProperties = DeviceJsonApi.GetProperties(device.Key); - if (deviceProperties == null || deviceProperties.ToLower().Contains("no device")) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(deviceProperties, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs index 66c5c3a8..172b8c4d 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DisableAllStreamDebugRequestHandler.cs @@ -1,31 +1,35 @@ using Crestron.SimplSharp.WebScripting; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DisableAllStreamDebugRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public DisableAllStreamDebugRequestHandler() - : base(true) + /// + /// Represents a DisableAllStreamDebugRequestHandler + /// + public class DisableAllStreamDebugRequestHandler : WebApiBaseRequestHandler { - } + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public DisableAllStreamDebugRequestHandler() + : base(true) + { + } - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - DeviceManager.DisableAllDeviceStreamDebugging(); + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + DeviceManager.DisableAllDeviceStreamDebugging(); - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.End(); + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs index b52b0fd2..593ce4e7 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/DoNotLoadConfigOnNextBootRequestHandler.cs @@ -3,87 +3,91 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class DoNotLoadConfigOnNextBootRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// - /// Constructor + /// Represents a DoNotLoadConfigOnNextBootRequestHandler /// - /// - /// base(true) enables CORS support by default - /// - public DoNotLoadConfigOnNextBootRequestHandler() - : base(true) + public class DoNotLoadConfigOnNextBootRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var data = new Data + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public DoNotLoadConfigOnNextBootRequestHandler() + : base(true) { - DoNotLoadConfigOnNextBoot = Debug.DoNotLoadConfigOnNextBoot - }; - - var body = JsonConvert.SerializeObject(data, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(body, false); - context.Response.End(); - } - - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - if (context.Request.ContentLength < 0) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - var data = context.Request.GetRequestBody(); - if (string.IsNullOrEmpty(data)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var data = new Data + { + DoNotLoadConfigOnNextBoot = Debug.DoNotLoadConfigOnNextBoot + }; + + var body = JsonConvert.SerializeObject(data, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(body, false); context.Response.End(); - - return; } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - var d = new Data(); - var requestBody = JsonConvert.DeserializeAnonymousType(data, d); + return; + } - Debug.SetDoNotLoadConfigOnNextBoot(requestBody.DoNotLoadConfigOnNextBoot); + var data = context.Request.GetRequestBody(); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); - var responseBody = JsonConvert.SerializeObject(d, Formatting.Indented); + return; + } - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(responseBody, false); - context.Response.End(); + var d = new Data(); + var requestBody = JsonConvert.DeserializeAnonymousType(data, d); + + Debug.SetDoNotLoadConfigOnNextBoot(requestBody.DoNotLoadConfigOnNextBoot); + + var responseBody = JsonConvert.SerializeObject(d, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(responseBody, false); + context.Response.End(); + } } -} -/// -/// Represents a Data -/// -public class Data -{ /// - /// Gets or sets the DoNotLoadConfigOnNextBoot + /// Represents a Data /// - [JsonProperty("doNotLoadConfigOnNextBoot", NullValueHandling = NullValueHandling.Ignore)] - public bool DoNotLoadConfigOnNextBoot { get; set; } + public class Data + { + /// + /// Gets or sets the DoNotLoadConfigOnNextBoot + /// + [JsonProperty("doNotLoadConfigOnNextBoot", NullValueHandling = NullValueHandling.Ignore)] + public bool DoNotLoadConfigOnNextBoot { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs index 5e57b58b..9831fa91 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetFeedbacksForDeviceRequestHandler.cs @@ -1,101 +1,106 @@ -using System.Linq; +using System; +using System.Linq; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetFeedbacksForDeviceRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// - /// Constructor + /// Represents a GetFeedbacksForDeviceRequestHandler /// - /// - /// base(true) enables CORS support by default - /// - public GetFeedbacksForDeviceRequestHandler() - : base(true) + public class GetFeedbacksForDeviceRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetFeedbacksForDeviceRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - object deviceObj; - if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + + var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()) as IHasFeedback; + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var boolFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.BoolValue + }; + + var intFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.IntValue + }; + + var stringFeedback = + from feedback in device.Feedbacks.OfType() + where !string.IsNullOrEmpty(feedback.Key) + select new + { + FeedbackKey = feedback.Key, + Value = feedback.StringValue ?? string.Empty + }; + + var responseObj = new + { + BoolValues = boolFeedback, + IntValues = intFeedback, + SerialValues = stringFeedback + }; + + var js = JsonConvert.SerializeObject(responseObj, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); context.Response.End(); - - return; } - - - var device = DeviceManager.GetDeviceForKey(deviceObj.ToString()) as IHasFeedback; - if (device == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - var boolFeedback = - from feedback in device.Feedbacks.OfType() - where !string.IsNullOrEmpty(feedback.Key) - select new - { - FeedbackKey = feedback.Key, - Value = feedback.BoolValue - }; - - var intFeedback = - from feedback in device.Feedbacks.OfType() - where !string.IsNullOrEmpty(feedback.Key) - select new - { - FeedbackKey = feedback.Key, - Value = feedback.IntValue - }; - - var stringFeedback = - from feedback in device.Feedbacks.OfType() - where !string.IsNullOrEmpty(feedback.Key) - select new - { - FeedbackKey = feedback.Key, - Value = feedback.StringValue ?? string.Empty - }; - - var responseObj = new - { - BoolValues = boolFeedback, - IntValues = intFeedback, - SerialValues = stringFeedback - }; - - var js = JsonConvert.SerializeObject(responseObj, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs deleted file mode 100644 index c0d77e41..00000000 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetInitializationExceptionRequestHandler.cs +++ /dev/null @@ -1,50 +0,0 @@ - -using Crestron.SimplSharp.WebScripting; -using System.Collections.Generic; -using System.Text; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; -using PepperDash.Core.Web.RequestHandlers; -using System.Linq; - -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Request handler for retrieving initialization exceptions from the control system. -/// -public class GetInitializationExceptionsRequestHandler : WebApiBaseRequestHandler -{ - - /// - /// Initializes a new instance of the class. - /// - public GetInitializationExceptionsRequestHandler() : base(true) { } - - /// - protected override void HandleGet(HttpCwsContext context) - { - var exceptions = (Global.ControlSystem as IInitializationExceptions)?.InitializationExceptions ?? new List(); - - var response = Newtonsoft.Json.JsonConvert.SerializeObject(new - { - Exceptions = exceptions.Select(e => new - { - Message = e.Message, - StackTrace = e.StackTrace, - Type = e.GetType().FullName, - InnerException = e.InnerException != null ? new - { - Message = e.InnerException.Message, - StackTrace = e.InnerException.StackTrace, - Type = e.InnerException.GetType().FullName - } : null - }).ToList() - }); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(response, false); - context.Response.End(); - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs index ee7ec346..436215ca 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForBridgeKeyRequestHandler.cs @@ -4,74 +4,78 @@ using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; using PepperDash.Essentials.Core.Bridges; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetJoinMapForBridgeKeyRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public GetJoinMapForBridgeKeyRequestHandler() - : base(true) + /// + /// Represents a GetJoinMapForBridgeKeyRequestHandler + /// + public class GetJoinMapForBridgeKeyRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetJoinMapForBridgeKeyRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - object bridgeObj; - if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object bridgeObj; + if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; + if (bridge == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var joinMap = bridge.JoinMaps.Select(j => EssentialsWebApiHelpers.MapJoinToObject(j)).ToList(); + if (joinMap == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); context.Response.End(); - - return; - } - - var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; - if (bridge == null) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - var joinMap = bridge.JoinMaps.Select(j => EssentialsWebApiHelpers.MapJoinToObject(j)).ToList(); - if (joinMap == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs index 54996b8c..0cb81d38 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetJoinMapForDeviceKeyRequestHandler.cs @@ -3,92 +3,96 @@ using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; using PepperDash.Essentials.Core.Bridges; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetJoinMapForDeviceKeyRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public GetJoinMapForDeviceKeyRequestHandler() - : base(true) + /// + /// Represents a GetJoinMapForDeviceKeyRequestHandler + /// + public class GetJoinMapForDeviceKeyRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetJoinMapForDeviceKeyRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - object bridgeObj; - if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object bridgeObj; + if (!routeData.Values.TryGetValue("bridgeKey", out bridgeObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object deviceObj; + if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; + if (bridge == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + JoinMapBaseAdvanced deviceJoinMap; + if (!bridge.JoinMaps.TryGetValue(deviceObj.ToString(), out deviceJoinMap)) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + var joinMap = EssentialsWebApiHelpers.MapJoinToObject(deviceObj.ToString(), deviceJoinMap); + var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + TypeNameHandling = TypeNameHandling.None + }); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); context.Response.End(); - - return; } - - object deviceObj; - if (!routeData.Values.TryGetValue("deviceKey", out deviceObj)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - var bridge = DeviceManager.GetDeviceForKey(bridgeObj.ToString()) as EiscApiAdvanced; - if (bridge == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - JoinMapBaseAdvanced deviceJoinMap; - if (!bridge.JoinMaps.TryGetValue(deviceObj.ToString(), out deviceJoinMap)) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.End(); - - return; - } - - var joinMap = EssentialsWebApiHelpers.MapJoinToObject(deviceObj.ToString(), deviceJoinMap); - var js = JsonConvert.SerializeObject(joinMap, Formatting.Indented, new JsonSerializerSettings - { - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - TypeNameHandling = TypeNameHandling.None - }); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs index f89b0919..eeea75cd 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetPackageManifestRequestHandler.cs @@ -6,256 +6,254 @@ using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Represents a GetPackageManifestRequestHandler -/// -public class GetPackageManifestRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// - /// Constructor + /// Represents a GetPackageManifestRequestHandler /// - /// - /// base(true) enables CORS support by default - /// - public GetPackageManifestRequestHandler() - : base(true) + public class GetPackageManifestRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - try + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetPackageManifestRequestHandler() + : base(true) { - var result = CloneVersionData(ConfigReader.ConfigObject?.Versions) ?? new VersionData(); - - PopulateEssentials(result); - PopulatePackages(result); - - var js = JsonConvert.SerializeObject(result, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); - } - catch (Exception ex) - { - PepperDash.Core.Debug.LogMessage(ex, "Exception handling GET /packageManifest request"); - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.End(); - } - } - - /// - /// Deep-copies the config's VersionData so the live config object is never mutated - /// - private static VersionData CloneVersionData(VersionData source) - { - if (source == null) - { - return null; } - var json = JsonConvert.SerializeObject(source); - return JsonConvert.DeserializeObject(json); - } - - /// - /// Enriches (or creates) the essentials entry from the loaded PepperDash.Essentials.Core assembly - /// - private static void PopulateEssentials(VersionData result) - { - var essentials = result.Essentials ?? new PackageVersion(); - - essentials.Version = Global.AssemblyVersion; - - // The main program assembly (PackageId "PepperDashEssentials") is what's actually published - // to NuGet, but this handler lives in PepperDash.Essentials.Core, which can't reference that - // project's types directly (Essentials -> Core, not the reverse). PluginLoader.EssentialsAssembly.Assembly - // is unreliable (often left null - see PluginLoader.SetEssentialsAssembly), so look it up - // directly from the loaded AppDomain by its Directory.Build.props-embedded PackageId metadata - // (every project's .csproj sets its own PackageId explicitly), falling back to this handler's - // own (Core) assembly if it can't be found. - var essentialsAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => string.Equals(GetAssemblyMetadataValue(a, "PackageId"), "PepperDashEssentials", StringComparison.OrdinalIgnoreCase)) - ?? typeof(GetPackageManifestRequestHandler).Assembly; - - var repoUrl = TrimTrailingGit(GetAssemblyMetadataValue(essentialsAssembly, "RepositoryUrl")); - if (!string.IsNullOrEmpty(repoUrl)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - essentials.RepoUrl = repoUrl; - } - - var name = GetAssemblyProduct(essentialsAssembly); - if (!string.IsNullOrEmpty(name)) - { - essentials.Name = name; - } - - // Prefer the PackageId embedded via Directory.Build.props' AssemblyMetadata item (the - // authoritative source, matching the actual published PackageId) over any config-supplied - // or hardcoded value. - var reflectedPackageId = GetAssemblyMetadataValue(essentialsAssembly, "PackageId"); - if (!string.IsNullOrEmpty(reflectedPackageId)) - { - essentials.PackageId = reflectedPackageId; - } - else if (string.IsNullOrEmpty(essentials.PackageId)) - { - essentials.PackageId = "PepperDashEssentials"; - } - - result.Essentials = essentials; - } - - /// - /// Merges reflection data from loaded plugin assemblies with the config's packages list - /// - private static void PopulatePackages(VersionData result) - { - // Filter out null entries defensively - the packages list is deserialized from user-editable - // config JSON, so a malformed "packages": [null, ...] shouldn't throw and 500 the endpoint. - var configPackages = (result.Packages ?? new System.Collections.Generic.List()) - .Where(p => p != null) - .ToList(); - var matchedConfigPackages = new System.Collections.Generic.HashSet(); - var mergedPackages = new System.Collections.Generic.List(); - - foreach (var loaded in PluginLoader.EssentialsPluginAssemblies.Where(a => a.Assembly != null)) - { - var reflectedVersion = loaded.Version; - if (string.IsNullOrEmpty(reflectedVersion)) + try { - // Never emit an entry with no version - the extension's parser drops entries - // whose version isn't a string. - continue; + var result = CloneVersionData(ConfigReader.ConfigObject?.Versions) ?? new VersionData(); + + PopulateEssentials(result); + PopulatePackages(result); + + var js = JsonConvert.SerializeObject(result, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } + catch (Exception ex) + { + PepperDash.Core.Debug.LogMessage(ex, "Exception handling GET /packageManifest request"); + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + } + } + + /// + /// Deep-copies the config's VersionData so the live config object is never mutated + /// + private static VersionData CloneVersionData(VersionData source) + { + if (source == null) + { + return null; } - var reflectedRepoUrl = TrimTrailingGit(GetAssemblyMetadataValue(loaded.Assembly, "RepositoryUrl")); - var reflectedName = GetAssemblyProduct(loaded.Assembly); + var json = JsonConvert.SerializeObject(source); + return JsonConvert.DeserializeObject(json); + } - // Plugins built from a Directory.Build.props that embeds - // carry their PackageId - // directly - this is authoritative and should be preferred over the title/name fallback chain. - var reflectedPackageId = GetAssemblyMetadataValue(loaded.Assembly, "PackageId"); + /// + /// Enriches (or creates) the essentials entry from the loaded PepperDash.Essentials.Core assembly + /// + private static void PopulateEssentials(VersionData result) + { + var essentials = result.Essentials ?? new NugetVersion(); - var assemblyTitle = GetAssemblyTitle(loaded.Assembly); - var assemblyName = loaded.Assembly.GetName().Name; - var assemblyNameNoSeriesSuffix = StripTrailingSeriesSuffix(assemblyName); + essentials.Version = Global.AssemblyVersion; - var match = configPackages.FirstOrDefault(p => - !matchedConfigPackages.Contains(p) && - !string.IsNullOrEmpty(p.PackageId) && - (string.Equals(p.PackageId, reflectedPackageId, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyTitle, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyName, StringComparison.OrdinalIgnoreCase) || - string.Equals(p.PackageId, assemblyNameNoSeriesSuffix, StringComparison.OrdinalIgnoreCase))); + // The main program assembly (PackageId "PepperDashEssentials") is what's actually published + // to NuGet, but this handler lives in PepperDash.Essentials.Core, which can't reference that + // project's types directly (Essentials -> Core, not the reverse). PluginLoader.EssentialsAssembly.Assembly + // is unreliable (often left null - see PluginLoader.SetEssentialsAssembly), so look it up + // directly from the loaded AppDomain by its Directory.Build.props-embedded PackageId metadata + // (every project's .csproj sets its own PackageId explicitly), falling back to this handler's + // own (Core) assembly if it can't be found. + var essentialsAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => string.Equals(GetAssemblyMetadataValue(a, "PackageId"), "PepperDashEssentials", StringComparison.OrdinalIgnoreCase)) + ?? typeof(GetPackageManifestRequestHandler).Assembly; - if (match != null) + var repoUrl = TrimTrailingGit(GetAssemblyMetadataValue(essentialsAssembly, "RepositoryUrl")); + if (!string.IsNullOrEmpty(repoUrl)) { - matchedConfigPackages.Add(match); + essentials.RepoUrl = repoUrl; + } - mergedPackages.Add(new PackageVersion + var name = GetAssemblyProduct(essentialsAssembly); + if (!string.IsNullOrEmpty(name)) + { + essentials.Name = name; + } + + // Prefer the PackageId embedded via Directory.Build.props' AssemblyMetadata item (the + // authoritative source, matching the actual published PackageId) over any config-supplied + // or hardcoded value. + var reflectedPackageId = GetAssemblyMetadataValue(essentialsAssembly, "PackageId"); + if (!string.IsNullOrEmpty(reflectedPackageId)) + { + essentials.PackageId = reflectedPackageId; + } + else if (string.IsNullOrEmpty(essentials.PackageId)) + { + essentials.PackageId = "PepperDashEssentials"; + } + + result.Essentials = essentials; + } + + /// + /// Merges reflection data from loaded plugin assemblies with the config's packages list + /// + private static void PopulatePackages(VersionData result) + { + // Filter out null entries defensively - the packages list is deserialized from user-editable + // config JSON, so a malformed "packages": [null, ...] shouldn't throw and 500 the endpoint. + var configPackages = (result.Packages ?? new System.Collections.Generic.List()) + .Where(p => p != null) + .ToList(); + var matchedConfigPackages = new System.Collections.Generic.HashSet(); + var mergedPackages = new System.Collections.Generic.List(); + + foreach (var loaded in PluginLoader.EssentialsPluginAssemblies.Where(a => a.Assembly != null)) + { + var reflectedVersion = loaded.Version; + if (string.IsNullOrEmpty(reflectedVersion)) { - Name = !string.IsNullOrEmpty(match.Name) ? match.Name : reflectedName, - RepoUrl = !string.IsNullOrEmpty(match.RepoUrl) ? match.RepoUrl : reflectedRepoUrl, - PackageId = !string.IsNullOrEmpty(reflectedPackageId) ? reflectedPackageId : match.PackageId, - Version = reflectedVersion - }); - } - else - { - // Loaded but not present (or not matched) in config - emit the reflected PackageId - // when the assembly carries one, otherwise leave it null as before. - mergedPackages.Add(new PackageVersion + // Never emit an entry with no version - the extension's parser drops entries + // whose version isn't a string. + continue; + } + + var reflectedRepoUrl = TrimTrailingGit(GetAssemblyMetadataValue(loaded.Assembly, "RepositoryUrl")); + var reflectedName = GetAssemblyProduct(loaded.Assembly); + + // Plugins built from a Directory.Build.props that embeds + // carry their PackageId + // directly - this is authoritative and should be preferred over the title/name fallback chain. + var reflectedPackageId = GetAssemblyMetadataValue(loaded.Assembly, "PackageId"); + + var assemblyTitle = GetAssemblyTitle(loaded.Assembly); + var assemblyName = loaded.Assembly.GetName().Name; + var assemblyNameNoSeriesSuffix = StripTrailingSeriesSuffix(assemblyName); + + var match = configPackages.FirstOrDefault(p => + !matchedConfigPackages.Contains(p) && + !string.IsNullOrEmpty(p.PackageId) && + (string.Equals(p.PackageId, reflectedPackageId, StringComparison.OrdinalIgnoreCase) || + string.Equals(p.PackageId, assemblyTitle, StringComparison.OrdinalIgnoreCase) || + string.Equals(p.PackageId, assemblyName, StringComparison.OrdinalIgnoreCase) || + string.Equals(p.PackageId, assemblyNameNoSeriesSuffix, StringComparison.OrdinalIgnoreCase))); + + if (match != null) { - Name = reflectedName, - RepoUrl = reflectedRepoUrl, - PackageId = reflectedPackageId, - Version = reflectedVersion, - }); + matchedConfigPackages.Add(match); + + mergedPackages.Add(new NugetVersion + { + Name = !string.IsNullOrEmpty(match.Name) ? match.Name : reflectedName, + RepoUrl = !string.IsNullOrEmpty(match.RepoUrl) ? match.RepoUrl : reflectedRepoUrl, + PackageId = !string.IsNullOrEmpty(reflectedPackageId) ? reflectedPackageId : match.PackageId, + Version = reflectedVersion + }); + } + else + { + // Loaded but not present (or not matched) in config - emit the reflected PackageId + // when the assembly carries one, otherwise leave it null as before. + mergedPackages.Add(new NugetVersion + { + Name = reflectedName, + RepoUrl = reflectedRepoUrl, + PackageId = reflectedPackageId, + Version = reflectedVersion, + }); + } } + + // Configured but not currently loaded - pass through unchanged + mergedPackages.AddRange(configPackages.Where(p => !matchedConfigPackages.Contains(p))); + + result.Packages = mergedPackages; } - // Configured but not currently loaded - pass through unchanged, but keep the same - // "never emit an entry with no version" guarantee as the loaded-reflection branch above - - // otherwise this branch reintroduces the schema issue that guard is meant to prevent. - mergedPackages.AddRange(configPackages.Where(p => - !matchedConfigPackages.Contains(p) && !string.IsNullOrEmpty(p.Version))); - - result.Packages = mergedPackages; - } - - private static string GetAssemblyMetadataValue(Assembly assembly, string key) - { - if (assembly == null) + private static string GetAssemblyMetadataValue(Assembly assembly, string key) { - return null; + if (assembly == null) + { + return null; + } + + var match = assembly.GetCustomAttributes(typeof(AssemblyMetadataAttribute), false) + .Cast() + .FirstOrDefault(a => string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase)); + + return match?.Value; } - var match = assembly.GetCustomAttributes(typeof(AssemblyMetadataAttribute), false) - .Cast() - .FirstOrDefault(a => string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase)); - - return match?.Value; - } - - private static string GetAssemblyProduct(Assembly assembly) - { - if (assembly == null) + private static string GetAssemblyProduct(Assembly assembly) { - return null; + if (assembly == null) + { + return null; + } + + var attribute = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false) + .FirstOrDefault() as AssemblyProductAttribute; + + return attribute?.Product; } - var attribute = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false) - .FirstOrDefault() as AssemblyProductAttribute; - - return attribute?.Product; - } - - private static string GetAssemblyTitle(Assembly assembly) - { - if (assembly == null) + private static string GetAssemblyTitle(Assembly assembly) { - return null; + if (assembly == null) + { + return null; + } + + var attribute = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false) + .FirstOrDefault() as AssemblyTitleAttribute; + + return attribute?.Title; } - var attribute = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false) - .FirstOrDefault() as AssemblyTitleAttribute; - - return attribute?.Title; - } - - private static string StripTrailingSeriesSuffix(string assemblyName) - { - const string suffix = ".4Series"; - - if (string.IsNullOrEmpty(assemblyName) || !assemblyName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + private static string StripTrailingSeriesSuffix(string assemblyName) { - return assemblyName; + const string suffix = ".4Series"; + + if (string.IsNullOrEmpty(assemblyName) || !assemblyName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + return assemblyName; + } + + return assemblyName.Substring(0, assemblyName.Length - suffix.Length); } - return assemblyName.Substring(0, assemblyName.Length - suffix.Length); - } - - private static string TrimTrailingGit(string repoUrl) - { - const string suffix = ".git"; - - if (string.IsNullOrEmpty(repoUrl) || !repoUrl.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + private static string TrimTrailingGit(string repoUrl) { - return repoUrl; - } + const string suffix = ".git"; - return repoUrl.Substring(0, repoUrl.Length - suffix.Length); + if (string.IsNullOrEmpty(repoUrl) || !repoUrl.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + return repoUrl; + } + + return repoUrl.Substring(0, repoUrl.Length - suffix.Length); + } } } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutesHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutesHandler.cs index c86777b3..bd264a7a 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutesHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutesHandler.cs @@ -3,49 +3,71 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetRoutesHandler:WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - private HttpCwsRouteCollection routeCollection; - private string basePath; - - public GetRoutesHandler(HttpCwsRouteCollection routeCollection, string basePath) { - this.routeCollection = routeCollection; - this.basePath = basePath; - } - - protected override void HandleGet(HttpCwsContext context) + /// + /// Represents a GetRoutesHandler + /// + public class GetRoutesHandler:WebApiBaseRequestHandler { - var currentIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); + private HttpCwsRouteCollection routeCollection; + private string basePath; - var hostname = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + /// + /// Constructor + /// + /// + /// + public GetRoutesHandler(HttpCwsRouteCollection routeCollection, string basePath) { + this.routeCollection = routeCollection; + this.basePath = basePath; + } - var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server - ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{basePath}" - : $"https://{currentIp}/cws{basePath}"; - - var response = JsonConvert.SerializeObject(new RoutesResponseObject() + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - Url = path, - Routes = routeCollection - }); + var currentIp = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - context.Response.StatusCode = 200; - context.Response.ContentType = "application/json"; - context.Response.Headers.Add("Content-Type", "application/json"); - context.Response.Write(response, false); - context.Response.End(); + var hostname = CrestronEthernetHelper.GetEthernetParameter( + CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0); + + var path = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server + ? $"https://{hostname}/VirtualControl/Rooms/{InitialParametersClass.RoomId}/cws{basePath}" + : $"https://{currentIp}/cws{basePath}"; + + var response = JsonConvert.SerializeObject(new RoutesResponseObject() + { + Url = path, + Routes = routeCollection + }); + + context.Response.StatusCode = 200; + context.Response.ContentType = "application/json"; + context.Response.Headers.Add("Content-Type", "application/json"); + context.Response.Write(response, false); + context.Response.End(); + } + } + + /// + /// Represents a RoutesResponseObject + /// + public class RoutesResponseObject + { + /// + /// Gets or sets the Url + /// + [JsonProperty("url")] + public string Url { set; get; } + + /// + /// Gets or sets the Routes + /// + [JsonProperty("routes")] + public HttpCwsRouteCollection Routes { get; set; } } } - -public class RoutesResponseObject -{ - [JsonProperty("url")] - public string Url { set; get; } - - [JsonProperty("routes")] - public HttpCwsRouteCollection Routes { get; set; } -} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs index 0c3e17e6..0d65b0e3 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingDevicesAndTieLinesHandler.cs @@ -5,500 +5,174 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -using PepperDash.Essentials.Core.Web; namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Handles HTTP requests to retrieve routing devices and tielines information - /// - public class GetRoutingDevicesAndTieLinesHandler : WebApiBaseRequestHandler + /// + /// Handles HTTP requests to retrieve routing devices and tielines information + /// + public class GetRoutingDevicesAndTieLinesHandler : WebApiBaseRequestHandler + { + public GetRoutingDevicesAndTieLinesHandler() : base(true) { } + + protected override void HandleGet(HttpCwsContext context) { + var devices = new List(); - /// - /// Initializes a new instance of the class. - /// - public GetRoutingDevicesAndTieLinesHandler() : base(true) { } - - /// - /// Handles the GET request to retrieve routing devices and tielines information - /// - /// - protected override void HandleGet(HttpCwsContext context) + // Get all devices from DeviceManager + foreach (var device in DeviceManager.AllDevices) + { + var deviceInfo = new RoutingDeviceInfo { - var devices = new List(); + Key = device.Key, + Name = (device as IKeyName)?.Name ?? device.Key + }; - // Tile-sink children of an IRoutingSinkWithLayouts device (e.g. a multiview decoder's - // per-window sinks) are rendered as synthesized input ports on the parent's node instead - // of as their own separate nodes - see BuildRoutingDeviceInfo below. - var tileChildren = RoutingGraphHelpers.BuildTileChildMap(); - - // Get all devices from DeviceManager - foreach (var device in DeviceManager.AllDevices) - { - if (tileChildren.ContainsKey(device.Key)) - continue; - - var deviceInfo = new RoutingDeviceInfo - { - Key = device.Key, - Name = (device as IKeyName)?.Name ?? device.Key - }; - - // Check if device implements IRoutingInputs - if (device is IRoutingInputs inputDevice) - { - deviceInfo.HasInputs = true; - deviceInfo.InputPorts = [.. inputDevice.InputPorts.Select(p => new PortInfo - { - Key = p.Key, - SignalType = p.Type.ToString(), - ConnectionType = p.ConnectionType.ToString(), - IsInternal = p.IsInternal - })]; - } - - // Check if device implements IRoutingOutputs - if (device is IRoutingOutputs outputDevice) - { - deviceInfo.HasOutputs = true; - deviceInfo.OutputPorts = [.. outputDevice.OutputPorts.Select(p => new PortInfo - { - Key = p.Key, - SignalType = p.Type.ToString(), - ConnectionType = p.ConnectionType.ToString(), - IsInternal = p.IsInternal - })]; - } - - // Check if device implements IRoutingMidpoint - if (device is IRoutingMidpoint) - { - deviceInfo.HasInputsAndOutputs = true; - } - - // Devices implementing IRoutingSinkWithLayouts (e.g. a multiview decoder) don't - // implement IRoutingInputs themselves - their tiles do. Synthesize one input port per - // tile (qualified so multiple tiles don't collide) so the graph can render a single - // node with one edge-target input per tile. - if (device is IRoutingSinkWithLayouts layoutDevice) - { - deviceInfo.HasInputs = true; - - var tilePorts = layoutDevice.WindowTileSinks - .OrderBy(kvp => kvp.Key) - .SelectMany(kvp => (kvp.Value as IRoutingInputs)?.InputPorts.Select(p => new PortInfo - { - Key = RoutingGraphHelpers.QualifyTilePortKey(kvp.Key, p.Key), - SignalType = p.Type.ToString(), - ConnectionType = p.ConnectionType.ToString(), - IsInternal = p.IsInternal - }) ?? []); - - deviceInfo.InputPorts = (deviceInfo.InputPorts ?? []).Concat(tilePorts).ToList(); - } - - // Only include devices that have routing capabilities - if (deviceInfo.HasInputs || deviceInfo.HasOutputs) - { - devices.Add(deviceInfo); - } - } - - // Get all tielines, remapping any that target a tile-sink child so they point at its - // IRoutingSinkWithLayouts parent's node/qualified port instead. - var tielines = TieLineCollection.Default.Select(tl => - { - var destinationDeviceKey = tl.DestinationPort.ParentDevice.Key; - var destinationPortKey = tl.DestinationPort.Key; - - if (tileChildren.TryGetValue(destinationDeviceKey, out var tileInfo)) - { - destinationDeviceKey = tileInfo.Parent.Key; - destinationPortKey = RoutingGraphHelpers.QualifyTilePortKey(tileInfo.TileNumber, destinationPortKey); - } - - return new TieLineInfo - { - SourceDeviceKey = tl.SourcePort.ParentDevice.Key, - SourcePortKey = tl.SourcePort.Key, - DestinationDeviceKey = destinationDeviceKey, - DestinationPortKey = destinationPortKey, - SignalType = tl.Type.ToString(), - IsInternal = tl.IsInternal - }; - }).ToList(); - - // Get current active routes from DefaultCollection, grouped by signal type - var currentRoutes = RouteDescriptorCollection.DefaultCollection.Descriptors - .GroupBy(d => d.SignalType.ToString()) - .Select(g => new CurrentRouteGroupInfo - { - SignalType = g.Key, - Routes = [.. g.Select(d => - { - var destinationDeviceKey = d.Destination.Key; - var destinationInputPortKey = d.InputPort?.Key; - - if (tileChildren.TryGetValue(destinationDeviceKey, out var tileInfo)) - { - destinationDeviceKey = tileInfo.Parent.Key; - if (destinationInputPortKey != null) - destinationInputPortKey = RoutingGraphHelpers.QualifyTilePortKey(tileInfo.TileNumber, destinationInputPortKey); - } - - return new ActiveRouteInfo - { - SourceDeviceKey = d.Source.Key, - DestinationDeviceKey = destinationDeviceKey, - DestinationInputPortKey = destinationInputPortKey, - Steps = [.. d.Routes.Select(r => - { - var switchingDeviceKey = r.SwitchingDevice?.Key; - var inputPortKey = r.InputPort?.Key; - - if (switchingDeviceKey != null && tileChildren.TryGetValue(switchingDeviceKey, out var stepTileInfo)) - { - switchingDeviceKey = stepTileInfo.Parent.Key; - if (inputPortKey != null) - inputPortKey = RoutingGraphHelpers.QualifyTilePortKey(stepTileInfo.TileNumber, inputPortKey); - } - - return new RouteSwitchStepInfo - { - SwitchingDeviceKey = switchingDeviceKey, - InputPortKey = inputPortKey, - OutputPortKey = r.OutputPort?.Key - }; - })] - }; - })] - }).ToList(); - - var response = new RoutingSystemInfo - { - Devices = devices, - TieLines = tielines, - CurrentRoutes = currentRoutes, - SinkCurrentSources = BuildSinkCurrentSources(tileChildren), - MultiviewLayouts = RoutingGraphHelpers.BuildMultiviewLayoutSnapshot() - }; - - var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(jsonResponse, false); - context.Response.End(); + // Check if device implements IRoutingInputs + if (device is IRoutingInputs inputDevice) + { + deviceInfo.HasInputs = true; + deviceInfo.InputPorts = inputDevice.InputPorts.Select(p => new PortInfo + { + Key = p.Key, + SignalType = p.Type.ToString(), + ConnectionType = p.ConnectionType.ToString(), + IsInternal = p.IsInternal + }).ToList(); } - /// - /// Builds current-source info for every sink device, read directly from each sink's own - /// bookkeeping (see ). - /// Unlike -based , this - /// also reflects routes made via a device-specific bulk API (e.g. - /// IHasDynamicMultiviewLayout.ApplyDynamicLayout) that never creates a - /// or at all, so it's what the dev tools app - /// should use to seed initial sink-routing state on page load. - /// - private static List BuildSinkCurrentSources( - Dictionary tileChildren) + // Check if device implements IRoutingOutputs + if (device is IRoutingOutputs outputDevice) { - var result = new List(); - - foreach (var device in DeviceManager.AllDevices.OfType()) - { - if (device.CurrentInputPort == null) - continue; - - var sourceKey = RoutingGraphHelpers.GetCurrentSourceKey(device); - if (string.IsNullOrEmpty(sourceKey)) - continue; - - var deviceKey = device.Key; - var inputPortKey = device.CurrentInputPort.Key; - - if (tileChildren.TryGetValue(deviceKey, out var tileInfo)) - { - deviceKey = tileInfo.Parent.Key; - inputPortKey = RoutingGraphHelpers.QualifyTilePortKey(tileInfo.TileNumber, inputPortKey); - } - - result.Add(new SinkCurrentSourceInfo - { - DeviceKey = deviceKey, - InputPortKey = inputPortKey, - SourceDeviceKey = sourceKey, - SignalType = device.CurrentInputPort.Type.ToString() - }); - } - - return result; + deviceInfo.HasOutputs = true; + deviceInfo.OutputPorts = outputDevice.OutputPorts.Select(p => new PortInfo + { + Key = p.Key, + SignalType = p.Type.ToString(), + ConnectionType = p.ConnectionType.ToString(), + IsInternal = p.IsInternal + }).ToList(); } + + // Check if device implements IRoutingInputsOutputs + if (device is IRoutingInputsOutputs) + { + deviceInfo.HasInputsAndOutputs = true; + } + + // Only include devices that have routing capabilities + if (deviceInfo.HasInputs || deviceInfo.HasOutputs) + { + devices.Add(deviceInfo); + } + } + + // Get all tielines + var tielines = TieLineCollection.Default.Select(tl => new TieLineInfo + { + SourceDeviceKey = tl.SourcePort.ParentDevice.Key, + SourcePortKey = tl.SourcePort.Key, + DestinationDeviceKey = tl.DestinationPort.ParentDevice.Key, + DestinationPortKey = tl.DestinationPort.Key, + SignalType = tl.Type.ToString(), + IsInternal = tl.IsInternal + }).ToList(); + + var response = new RoutingSystemInfo + { + Devices = devices, + TieLines = tielines + }; + + var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(jsonResponse, false); + context.Response.End(); } + } - /// - /// Represents the complete routing system information including devices and tielines - /// - public class RoutingSystemInfo - { + /// + /// Represents the complete routing system information including devices and tielines + /// + public class RoutingSystemInfo + { + [JsonProperty("devices")] + public List Devices { get; set; } - /// - /// Gets or sets the list of routing devices in the system, including their ports information - /// - [JsonProperty("devices")] - public List Devices { get; set; } + [JsonProperty("tieLines")] + public List TieLines { get; set; } + } + /// + /// Represents a routing device with its ports information + /// + public class RoutingDeviceInfo + { + [JsonProperty("key")] + public string Key { get; set; } - /// - /// Gets or sets the list of tielines in the system, including source/destination device and port information - /// - [JsonProperty("tieLines")] - public List TieLines { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - /// - /// Gets or sets the current active routes in the system, grouped by signal type - /// - [JsonProperty("currentRoutes")] - public List CurrentRoutes { get; set; } + [JsonProperty("hasInputs")] + public bool HasInputs { get; set; } - /// - /// Gets or sets the current source feeding each sink device, read directly from each sink's own - /// current-source bookkeeping. Covers routes made via device-specific bulk APIs (e.g. - /// dynamic multiview layouts) that does not. - /// - [JsonProperty("sinkCurrentSources")] - public List SinkCurrentSources { get; set; } + [JsonProperty("hasOutputs")] + public bool HasOutputs { get; set; } - /// - /// Gets or sets the current multiview canvas/tile layout for every device implementing - /// , keyed by device key. Devices with no currently - /// active layout are omitted. Lets a client render an initial visual mock-up of each - /// multiview decoder's monitor output without waiting for a routing feedback WebSocket - /// connection - see . - /// - [JsonProperty("multiviewLayouts")] - public Dictionary MultiviewLayouts { get; set; } - } + [JsonProperty("hasInputsAndOutputs")] + public bool HasInputsAndOutputs { get; set; } - /// - /// Represents the source currently feeding a single sink device's input port - /// - public class SinkCurrentSourceInfo - { - /// - /// Gets or sets the key of the sink device (or its IRoutingSinkWithLayouts parent, if this is a tile) - /// - [JsonProperty("deviceKey")] - public string DeviceKey { get; set; } + [JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List InputPorts { get; set; } - /// - /// Gets or sets the key of the input port currently receiving the source - /// - [JsonProperty("inputPortKey")] - public string InputPortKey { get; set; } + [JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List OutputPorts { get; set; } + } - /// - /// Gets or sets the key of the device currently feeding this input - /// - [JsonProperty("sourceDeviceKey")] - public string SourceDeviceKey { get; set; } + /// + /// Represents a routing port with its properties + /// + public class PortInfo + { + [JsonProperty("key")] + public string Key { get; set; } - /// - /// Gets or sets the signal type of the input port (e.g., AudioVideo, Audio, Video, etc.) - /// - [JsonProperty("signalType")] - public string SignalType { get; set; } - } + [JsonProperty("signalType")] + public string SignalType { get; set; } - /// - /// Represents a routing device with its ports information - /// - public class RoutingDeviceInfo : IKeyName - { + [JsonProperty("connectionType")] + public string ConnectionType { get; set; } - /// - [JsonProperty("key")] - public string Key { get; set; } + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + } - /// - [JsonProperty("name")] - public string Name { get; set; } + /// + /// Represents a tieline connection between two ports + /// + public class TieLineInfo + { + [JsonProperty("sourceDeviceKey")] + public string SourceDeviceKey { get; set; } - /// - /// Gets or sets a value indicating whether the device has routing input ports - /// - [JsonProperty("hasInputs")] - public bool HasInputs { get; set; } + [JsonProperty("sourcePortKey")] + public string SourcePortKey { get; set; } - /// - /// Gets or sets a value indicating whether the device has routing output ports - /// - [JsonProperty("hasOutputs")] - public bool HasOutputs { get; set; } + [JsonProperty("destinationDeviceKey")] + public string DestinationDeviceKey { get; set; } - /// - /// Gets or sets a value indicating whether the device has both routing inputs and outputs (e.g., matrix switcher) - /// - [JsonProperty("hasInputsAndOutputs")] - public bool HasInputsAndOutputs { get; set; } + [JsonProperty("destinationPortKey")] + public string DestinationPortKey { get; set; } - /// - /// Gets or sets the list of input ports for the device, if applicable. Null if the device does not have routing inputs. - /// - [JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)] - public List InputPorts { get; set; } + [JsonProperty("signalType")] + public string SignalType { get; set; } - /// - /// Gets or sets the list of output ports for the device, if applicable. Null if the device does not have routing outputs. - /// - [JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)] - public List OutputPorts { get; set; } - } - - /// - /// Represents a routing port with its properties - /// - public class PortInfo : IKeyed - { - /// - [JsonProperty("key")] - public string Key { get; set; } - - /// - /// Gets or sets the signal type of the port (e.g., AudioVideo, Audio, Video, etc.) - /// - [JsonProperty("signalType")] - public string SignalType { get; set; } - - /// - /// Gets or sets the connection type of the port (e.g., Hdmi, Dvi, Vga, etc.) - /// - [JsonProperty("connectionType")] - public string ConnectionType { get; set; } - - /// - /// Gets or sets a value indicating whether the port is internal - /// - [JsonProperty("isInternal")] - public bool IsInternal { get; set; } - } - - /// - /// Represents a tieline connection between two ports - /// - public class TieLineInfo - { - /// - /// Gets or sets the key of the source device for the tieline connection - /// - [JsonProperty("sourceDeviceKey")] - public string SourceDeviceKey { get; set; } - - - /// - /// Gets or sets the key of the source port for the tieline connection - /// - [JsonProperty("sourcePortKey")] - public string SourcePortKey { get; set; } - - /// - /// Gets or sets the key of the destination device for the tieline connection - /// - [JsonProperty("destinationDeviceKey")] - public string DestinationDeviceKey { get; set; } - - /// - /// Gets or sets the key of the destination port for the tieline connection - /// - [JsonProperty("destinationPortKey")] - public string DestinationPortKey { get; set; } - - /// - /// Gets or sets the signal type of the tieline connection (e.g., AudioVideo, Audio, Video, etc.) - /// - [JsonProperty("signalType")] - public string SignalType { get; set; } - - /// - /// Gets or sets a value indicating whether the tieline connection is internal - /// - [JsonProperty("isInternal")] - public bool IsInternal { get; set; } - } - - /// - /// Represents a group of active routes for a given signal type - /// - public class CurrentRouteGroupInfo - { - /// - /// Gets or sets the signal type for the group of active routes (e.g., AudioVideo, Audio, Video, etc.) - /// - [JsonProperty("signalType")] - public string SignalType { get; set; } - - /// - /// Gets or sets the list of active routes for the given signal type - /// - [JsonProperty("routes")] - public List Routes { get; set; } - } - - /// - /// Represents a single active route from a source to a destination - /// - public class ActiveRouteInfo - { - /// - /// Gets or sets the key of the source device for the active route - /// - [JsonProperty("sourceDeviceKey")] - public string SourceDeviceKey { get; set; } - - /// - /// Gets or sets the key of the destination device for the active route - /// - [JsonProperty("destinationDeviceKey")] - public string DestinationDeviceKey { get; set; } - - /// - /// Gets or sets the key of the destination input port for the active route, if applicable - /// - [JsonProperty("destinationInputPortKey", NullValueHandling = NullValueHandling.Ignore)] - public string DestinationInputPortKey { get; set; } - - /// - /// Gets or sets the list of switching steps for the active route - /// - [JsonProperty("steps")] - public List Steps { get; set; } - } - - /// - /// Represents a single switching step within a route - /// - public class RouteSwitchStepInfo - { - /// - /// Gets or sets the key of the switching device for the route step - /// - [JsonProperty("switchingDeviceKey", NullValueHandling = NullValueHandling.Ignore)] - public string SwitchingDeviceKey { get; set; } - - - /// - /// Gets or sets the key of the input port for the route step, if applicable - /// - [JsonProperty("inputPortKey", NullValueHandling = NullValueHandling.Ignore)] - public string InputPortKey { get; set; } - - /// - /// Gets or sets the key of the output port for the route step, if applicable - /// - [JsonProperty("outputPortKey", NullValueHandling = NullValueHandling.Ignore)] - public string OutputPortKey { get; set; } - } -} \ No newline at end of file + [JsonProperty("isInternal")] + public bool IsInternal { get; set; } + } +} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingPortsHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingPortsHandler.cs index b0020c75..8cee7e9e 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingPortsHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetRoutingPortsHandler.cs @@ -5,66 +5,83 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetRoutingPortsHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - public GetRoutingPortsHandler() : base(true) { } - - protected override void HandleGet(HttpCwsContext context) + /// + /// Represents a GetRoutingPortsHandler + /// + public class GetRoutingPortsHandler : WebApiBaseRequestHandler { - var routeData = context.Request.RouteData; + /// + /// Constructor + /// + public GetRoutingPortsHandler() : base(true) { } - if (routeData == null) + /// + /// Handles the GET request + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + return; + } + + if(!routeData.Values.TryGetValue("deviceKey", out var deviceKey)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + return; + } + + var device = DeviceManager.GetDeviceForKey(deviceKey.ToString()); + + if (device == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Device Not Found"; + context.Response.End(); + return; + } + + var inputPorts = (device as IRoutingInputs)?.InputPorts; + var outputPorts = (device as IRoutingOutputs)?.OutputPorts; + + var response = JsonConvert.SerializeObject( new ReturnValue + { + InputPorts = inputPorts?.Select(p => p.Key).ToList(), + OutputPorts = outputPorts?.Select(p => p.Key).ToList() + }); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(response, false); context.Response.End(); - return; + } + } - if (!routeData.Values.TryGetValue("deviceKey", out var deviceKey)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - return; - } - - var device = DeviceManager.GetDeviceForKey(deviceKey.ToString()); - - if (device == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Device Not Found"; - context.Response.End(); - return; - } - - var inputPorts = (device as IRoutingInputs)?.InputPorts; - var outputPorts = (device as IRoutingOutputs)?.OutputPorts; - - var response = JsonConvert.SerializeObject(new ReturnValue - { - InputPorts = inputPorts?.Select(p => p.Key).ToList(), - OutputPorts = outputPorts?.Select(p => p.Key).ToList() - }); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(response, false); - context.Response.End(); + internal class ReturnValue + { + /// + /// Gets or sets the InputPorts + /// + [JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List InputPorts { get; set; } + /// + /// Gets or sets the OutputPorts + /// + [JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)] + public List OutputPorts { get; set; } } } - -internal class ReturnValue -{ - [JsonProperty("inputPorts", NullValueHandling = NullValueHandling.Ignore)] - public List InputPorts { get; set; } - - [JsonProperty("outputPorts", NullValueHandling = NullValueHandling.Ignore)] - public List OutputPorts { get; set; } -} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs index c8e02126..f7088cb2 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTieLinesRequestHandler.cs @@ -1,32 +1,43 @@ -using System.Linq; -using System.Text; -using Crestron.SimplSharp.WebScripting; +using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; +using System.Linq; +using System.Text; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -public class GetTieLinesRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - public GetTieLinesRequestHandler() : base(true) { } - - protected override void HandleGet(HttpCwsContext context) + /// + /// Represents a GetTieLinesRequestHandler + /// + public class GetTieLinesRequestHandler : WebApiBaseRequestHandler { - var tieLineString = JsonConvert.SerializeObject(TieLineCollection.Default.Select((tl) => new + /// + /// Constructor + /// + public GetTieLinesRequestHandler() : base(true) { } + + /// + /// Handles the GET request + /// + /// + protected override void HandleGet(HttpCwsContext context) { - sourceKey = tl.SourcePort.ParentDevice.Key, - sourcePort = tl.SourcePort.Key, - destinationKey = tl.DestinationPort.ParentDevice.Key, - destinationPort = tl.DestinationPort.Key, - type = tl.Type.ToString(), - })); + var tieLineString = JsonConvert.SerializeObject(TieLineCollection.Default.Select((tl) => new + { + sourceKey = tl.SourcePort.ParentDevice.Key, + sourcePort = tl.SourcePort.Key, + destinationKey = tl.DestinationPort.ParentDevice.Key, + destinationPort = tl.DestinationPort.Key, + type = tl.Type.ToString(), + })); - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(tieLineString, false); - context.Response.End(); + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(tieLineString, false); + context.Response.End(); + } } } diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs index fff21d17..2cabeb5e 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesByFilterRequestHandler.cs @@ -3,68 +3,69 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests for types of devices based on a filter. The filter is used to determine which device factory to pull types from. The filter is passed as part of the URL and is accessed via the RouteData property of the HttpCwsContext. -/// -public class GetTypesByFilterRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public GetTypesByFilterRequestHandler() - : base(true) + /// + /// Represents a GetTypesByFilterRequestHandler + /// + public class GetTypesByFilterRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetTypesByFilterRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - object filterObj; - if (!routeData.Values.TryGetValue("filter", out filterObj)) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + object filterObj; + if (!routeData.Values.TryGetValue("filter", out filterObj)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(filterObj.ToString()); + if (deviceFactory == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); + var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); context.Response.End(); - - return; } - - var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(filterObj.ToString()); - if (deviceFactory == null) - { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; - context.Response.End(); - - return; - } - - var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); - var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesRequestHandler.cs index e60700a1..6d011d23 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/GetTypesRequestHandler.cs @@ -3,58 +3,59 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests for types of devices. This handler is used when no filter is needed to determine which device factory to pull types from. If a filter is needed, use the GetTypesByFilterRequestHandler instead. -/// -public class GetTypesRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public GetTypesRequestHandler() - : base(true) + /// + /// Represents a GetTypesRequestHandler + /// + public class GetTypesRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var routeData = context.Request.RouteData; - if (routeData == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public GetTypesRequestHandler() + : base(true) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; } - var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(null); - if (deviceFactory == null) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; + var routeData = context.Request.RouteData; + if (routeData == null) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var deviceFactory = DeviceFactory.GetDeviceFactoryDictionary(null); + if (deviceFactory == null) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); + var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); context.Response.End(); - - return; } - - var deviceTypes = deviceFactory.Select(t => EssentialsWebApiHelpers.MapDeviceTypeToObject(t)).ToList(); - var js = JsonConvert.SerializeObject(deviceTypes, Formatting.Indented); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoadConfigRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoadConfigRequestHandler.cs index adc68c66..7706973e 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoadConfigRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoadConfigRequestHandler.cs @@ -1,38 +1,42 @@ -using Crestron.SimplSharp.WebScripting; +using Crestron.SimplSharp; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests to load the config. This is used when the config is loaded via the web API instead of at startup. This is typically used in conjunction with the SaveConfigRequestHandler to allow for saving and loading of the config via the web API. -/// -public class LoadConfigRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public LoadConfigRequestHandler() - : base(true) + /// + /// Represents a LoadConfigRequestHandler + /// + public class LoadConfigRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - var message = ""; - var cs = Global.ControlSystem as ILoadConfig; - if (cs != null) - cs.GoWithLoad(); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(message, false); - context.Response.End(); + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public LoadConfigRequestHandler() + : base(true) + { + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + var message = ""; + var cs = Global.ControlSystem as ILoadConfig; + if(cs != null) + cs.GoWithLoad(); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(message, false); + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs index 18e62d63..949f8bd5 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs @@ -1,121 +1,173 @@ using System; +using System.Collections.Generic; using Crestron.SimplSharp.CrestronAuthentication; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Represents a LoginRequestHandler -/// -public class LoginRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// - /// Constructor + /// Represents a LoginRequestHandler /// - /// - /// base(true) enables CORS support by default - /// - public LoginRequestHandler() - : base(true) + public class LoginRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles POST method requests for user login and token generation - /// - /// The HTTP context for the request. - protected override void HandlePost(HttpCwsContext context) - { - try + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public LoginRequestHandler() + : base(true) { - if (context.Request.ContentLength < 0) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - var data = context.Request.GetRequestBody(); - if (string.IsNullOrEmpty(data)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - var loginRequest = JsonConvert.DeserializeObject(data); - - if (loginRequest == null || string.IsNullOrEmpty(loginRequest.Username) || string.IsNullOrEmpty(loginRequest.Password)) - { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; - context.Response.End(); - - return; - } - - Authentication.UserToken token; + } + /// + /// Handles POST method requests for user login and token generation + /// + /// The HTTP context for the request. + protected override void HandlePost(HttpCwsContext context) + { try { - token = Authentication.GetAuthenticationToken(loginRequest.Username, loginRequest.Password); - } - catch (ArgumentException) - { - context.Response.StatusCode = 401; - context.Response.StatusDescription = "Bad Request"; + if (context.Request.ContentLength < 0) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var data = context.Request.GetRequestBody(); + if (string.IsNullOrEmpty(data)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + var loginRequest = JsonConvert.DeserializeObject(data); + + if (loginRequest == null || string.IsNullOrEmpty(loginRequest.Username) || string.IsNullOrEmpty(loginRequest.Password)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + Authentication.UserToken token; + + try + { + token = Authentication.GetAuthenticationToken(loginRequest.Username, loginRequest.Password); + } + catch (ArgumentException) + { + context.Response.StatusCode = 401; + context.Response.StatusDescription = "Bad Request"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(JsonConvert.SerializeObject(new { Error = "Unauthorized" }, Formatting.Indented), false); + context.Response.End(); + return; + } + + if (!token.Valid) + { + context.Response.StatusCode = 401; + context.Response.StatusDescription = "Unauthorized"; + context.Response.End(); + + return; + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(JsonConvert.SerializeObject( + new + { + Token = new LoginResponse + { + UserName = token.UserName, + Access = token.Access, + State = token.State, + Groups = token.Groups, + ADConnect = token.ADConnect, + Valid = token.Valid + } + }, Formatting.Indented), false); context.Response.End(); - - return; } - - if (!token.Valid) + catch (System.Exception ex) { - context.Response.StatusCode = 401; - context.Response.StatusDescription = "Unauthorized"; + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(JsonConvert.SerializeObject(new { Error = ex.Message }, Formatting.Indented), false); context.Response.End(); - - return; } - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(JsonConvert.SerializeObject(new { Token = token }, Formatting.Indented), false); - context.Response.End(); - } - catch (System.Exception ex) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(JsonConvert.SerializeObject(new { Error = ex.Message }, Formatting.Indented), false); - context.Response.End(); } } -} - -/// -/// Represents a LoginRequest -/// -public class LoginRequest -{ - /// - /// Gets or sets the username. - /// - public string Username { get; set; } /// - /// Gets or sets the password. + /// Represents a LoginRequest /// - public string Password { get; set; } -} + public class LoginRequest + { + /// + /// Gets or sets the username. + /// + public string Username { get; set; } + + /// + /// Gets or sets the password. + /// + public string Password { get; set; } + } + + /// + /// Represents a LoginResponse + /// + internal class LoginResponse + { + /// + /// Gets or sets the username. + /// + public string UserName { get; set; } + + /// + /// Gets or sets the access level. + /// + public Authentication.UserAuthenticationLevelEnum Access { get; set; } + + /// + /// Gets or sets the token authenticated state. + /// + public Authentication.eTokenAuthenticatedState State { get; set; } + + /// + /// Gets or sets the list of groups. + /// + public List Groups { get; set; } + + /// + /// Gets or sets the active directory connection flag. + /// + public int ADConnect { get; set; } + + /// + /// Gets or sets the valid flag indicating whether the token is valid. + /// + public bool Valid { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ReportVersionsRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ReportVersionsRequestHandler.cs index 00c33044..ace863ab 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ReportVersionsRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ReportVersionsRequestHandler.cs @@ -3,49 +3,50 @@ using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests for reporting versions of loaded assemblies. This is used to report the versions of the assemblies that are currently loaded in the system, which can be helpful for debugging and support purposes. -/// -public class ReportVersionsRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public ReportVersionsRequestHandler() - : base(true) + /// + /// Represents a ReportVersionsRequestHandler + /// + public class ReportVersionsRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var loadAssemblies = PluginLoader.LoadedAssemblies; - if (loadAssemblies == null) + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public ReportVersionsRequestHandler() + : base(true) { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.End(); - - return; } - var assemblies = loadAssemblies.Select(a => EssentialsWebApiHelpers.MapToAssemblyObject(a)).ToList(); + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var loadAssemblies = PluginLoader.LoadedAssemblies; + if (loadAssemblies == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); - var js = JsonConvert.SerializeObject(assemblies, Formatting.Indented); + return; + } - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(js, false); - context.Response.End(); + var assemblies = loadAssemblies.Select(a => EssentialsWebApiHelpers.MapToAssemblyObject(a)).ToList(); + + var js = JsonConvert.SerializeObject(assemblies, Formatting.Indented); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(js, false); + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/RestartProgramRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/RestartProgramRequestHandler.cs index a094657b..9f853e61 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/RestartProgramRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/RestartProgramRequestHandler.cs @@ -1,39 +1,41 @@ using Crestron.SimplSharp; using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests to restart the program. -/// This is used when the program needs to be restarted via the web API instead of manually restarting the program. -/// -public class RestartProgramRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public RestartProgramRequestHandler() - : base(true) + /// + /// Represents a RestartProgramRequestHandler + /// + public class RestartProgramRequestHandler : WebApiBaseRequestHandler { - } - - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - var message = ""; - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) - CrestronConsole.SendControlSystemCommand($"progres -p:{InitialParametersClass.ApplicationNumber}", ref message); - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(message, false); - context.Response.End(); + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public RestartProgramRequestHandler() + : base(true) + { + } + + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) + { + var message = ""; + if(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + CrestronConsole.SendControlSystemCommand($"progres -p:{InitialParametersClass.ApplicationNumber}", ref message); + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.Write(message, false); + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/RoutingFeedbackSessionRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/RoutingFeedbackSessionRequestHandler.cs deleted file mode 100644 index d095b78f..00000000 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/RoutingFeedbackSessionRequestHandler.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.WebScripting; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Web.RequestHandlers; -using Serilog.Events; - -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles HTTP requests to start and stop the routing feedback WebSocket session. -/// GET starts the server and returns connection URLs. POST stops the session. -/// Automatically configures port forwarding for Crestron processors with a CS LAN adapter. -/// -public class RoutingFeedbackSessionRequestHandler : WebApiBaseRequestHandler -{ - private static readonly RoutingFeedbackWebsocket _instance = new RoutingFeedbackWebsocket(); - private CTimer _portForwardTimeoutTimer; - private readonly object _timerLock = new object(); - - /// - /// Constructor - /// - public RoutingFeedbackSessionRequestHandler() - : base(true) - { - } - - /// - /// Starts the routing feedback WebSocket server and returns the connection URL. - /// - protected override void HandleGet(HttpCwsContext context) - { - try - { - var ip = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - - var port = 0; - - if (!_instance.IsRunning) - { - Debug.LogMessage(LogEventLevel.Information, "Starting Routing Feedback WS Server"); - port = new Random().Next(65335, 65434); - _instance.StartServerAndSetPort(port); - } - else - { - port = _instance.Port; - } - - // Always ensure port forwarding is active — it may have been removed by timeout - string csIp = null; - try - { - var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( - EthernetAdapterType.EthernetCSAdapter); - csIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - - if (port > 0) - { - var result = CrestronEthernetHelper.AddPortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error forwarding port {0} to CS LAN: {1}", port, result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port {0} forwarded to CS LAN for routing feedback websocket", port); - StartPortForwardTimeout(port, csIp); - } - } - } - catch (ArgumentException) - { - Debug.LogMessage(LogEventLevel.Debug, "This processor does not have a CS LAN adapter; skipping port forwarding"); - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error automatically forwarding routing feedback websocket port to CS LAN: {0}", ex.Message); - } - - if (!_instance.IsRunning) - { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.Write( - JsonConvert.SerializeObject(new { error = "Failed to start routing feedback WebSocket server. Check logs for details." }), - false); - context.Response.End(); - return; - } - - var url = _instance.Url; - - var data = new - { - url, - fallbackUrl = csIp != null ? url.Replace(csIp, ip) : null - }; - - Debug.LogMessage(LogEventLevel.Information, "Routing Feedback Session URL: {0}", url); - if (data.fallbackUrl != null) - Debug.LogMessage(LogEventLevel.Information, "Routing Feedback Fallback URL: {0}", data.fallbackUrl); - - var res = JsonConvert.SerializeObject(data); - - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.Write(res, false); - context.Response.End(); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Error, "Error handling routing feedback session request: {0}", e); - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; - context.Response.End(); - } - } - - /// - /// Stops the routing feedback WebSocket session and removes port forwarding. - /// - protected override void HandlePost(HttpCwsContext context) - { - CancelPortForwardTimeout(); - - var port = _instance.Port; - - _instance.StopServer(); - - // Remove port forwarding if CS LAN exists - try - { - var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType( - EthernetAdapterType.EthernetCSAdapter); - var csIp = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId); - - var result = CrestronEthernetHelper.RemovePortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for routing port {0}: {1}", port, result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for routing port {0} removed", port); - } - } - catch (ArgumentException) - { - // No CS LAN adapter - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding for routing: {0}", ex.Message); - } - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.End(); - - Debug.LogMessage(LogEventLevel.Information, "Routing Feedback WebSocket Session Stopped"); - } - - private void StartPortForwardTimeout(int port, string csIp) - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = new CTimer(_ => - { - if (_instance.HasActiveConnections) - { - Debug.LogMessage(LogEventLevel.Debug, "Routing feedback websocket has active connections; keeping port forward"); - StartPortForwardTimeout(port, csIp); - return; - } - - Debug.LogMessage(LogEventLevel.Information, "No routing feedback websocket connection within 30 seconds; removing port forward for port {0}", port); - - try - { - var result = CrestronEthernetHelper.RemovePortForwarding( - (ushort)port, (ushort)port, csIp, - CrestronEthernetHelper.ePortMapTransport.TCP); - - if (result != CrestronEthernetHelper.PortForwardingUserPatRetCodes.NoErr) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", result); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Port forwarding for routing port {0} removed due to timeout", port); - } - } - catch (Exception ex) - { - Debug.LogMessage(LogEventLevel.Warning, "Error removing port forwarding on timeout: {0}", ex.Message); - } - }, 120000); - } - } - - private void CancelPortForwardTimeout() - { - lock (_timerLock) - { - _portForwardTimeoutTimer?.Dispose(); - _portForwardTimeoutTimer = null; - } - } -} diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ServeDebugAppRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ServeDebugAppRequestHandler.cs index 85b17bd6..d8a296e5 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ServeDebugAppRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ServeDebugAppRequestHandler.cs @@ -8,18 +8,18 @@ using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; using Serilog.Events; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Serves the React debug app from the processor's /HTML/debug folder. -/// The root route (debug) and all sub-paths (debug/{*filePath}) are handled here. -/// Text assets are sent as UTF-8 strings; binary assets are written to the response -/// OutputStream. Any sub-path that does not match a real file falls back to -/// index.html so that client-side (React Router) routing continues to work. -/// -public class ServeDebugAppRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - private static readonly Dictionary MimeTypes = new(StringComparer.OrdinalIgnoreCase) + /// + /// Serves the React debug app from the processor's /HTML/debug folder. + /// The root route (debug) and all sub-paths (debug/{*filePath}) are handled here. + /// Text assets are sent as UTF-8 strings; binary assets are written to the response + /// OutputStream. Any sub-path that does not match a real file falls back to + /// index.html so that client-side (React Router) routing continues to work. + /// + public class ServeDebugAppRequestHandler : WebApiBaseRequestHandler + { + private static readonly Dictionary MimeTypes = new Dictionary(StringComparer.OrdinalIgnoreCase) { { ".html", "text/html; charset=utf-8" }, { ".htm", "text/html; charset=utf-8" }, @@ -41,189 +41,184 @@ public class ServeDebugAppRequestHandler : WebApiBaseRequestHandler { ".eot", "application/vnd.ms-fontobject" }, }; - private static readonly HashSet TextExtensions = new(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet TextExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".html", ".htm", ".js", ".mjs", ".jsx", ".css", ".json", ".map", ".svg", ".txt", ".xml" }; - /// - /// Constructor. CORS is enabled so browser dev-tools requests succeed. - /// - public ServeDebugAppRequestHandler() : base(true) { } + /// + /// Constructor. CORS is enabled so browser dev-tools requests succeed. + /// + public ServeDebugAppRequestHandler() : base(true) { } - /// - /// Handles GET requests for the debug app and its static assets. - /// - protected override void HandleGet(HttpCwsContext context) - { - // When acting as the server-level fallback handler, only handle - // requests that are actually for the /debug path; defer everything - // else to the base class (which returns 501 Not Implemented). - var rawUrl = context.Request.RawUrl ?? string.Empty; - if (rawUrl.IndexOf("/debug", StringComparison.OrdinalIgnoreCase) < 0) + /// + /// Handles GET requests for the debug app and its static assets. + /// + protected override void HandleGet(HttpCwsContext context) { - base.HandleGet(context); - return; - } - - try - { - var htmlDebugPath = GetHtmlDebugPath(); - if (htmlDebugPath == null) + // When acting as the server-level fallback handler, only handle + // requests that are actually for the /debug path; defer everything + // else to the base class (which returns 501 Not Implemented). + var rawUrl = context.Request.RawUrl ?? string.Empty; + if (rawUrl.IndexOf("/debug", StringComparison.OrdinalIgnoreCase) < 0) { - SendResponse(context, 500, "Internal Server Error"); + base.HandleGet(context); return; } - var requestedPath = GetRequestedFilePath(context); - - // Paths with no file extension are SPA client-side routes — serve index.html - string candidate; - if (string.IsNullOrEmpty(requestedPath) || !System.IO.Path.HasExtension(requestedPath)) + try { - candidate = System.IO.Path.Combine(htmlDebugPath, "index.html"); + var htmlDebugPath = GetHtmlDebugPath(); + if (htmlDebugPath == null) + { + SendResponse(context, 500, "Internal Server Error"); + return; + } + + var requestedPath = GetRequestedFilePath(context); + + // Paths with no file extension are SPA client-side routes — serve index.html + string candidate; + if (string.IsNullOrEmpty(requestedPath) || !System.IO.Path.HasExtension(requestedPath)) + { + candidate = System.IO.Path.Combine(htmlDebugPath, "index.html"); + } + else + { + var relativePart = requestedPath.Replace('/', System.IO.Path.DirectorySeparatorChar); + candidate = System.IO.Path.Combine(htmlDebugPath, relativePart); + } + + // Resolve to an absolute path and guard against path-traversal attacks + var resolvedCandidate = System.IO.Path.GetFullPath(candidate); + var resolvedBase = System.IO.Path.GetFullPath(htmlDebugPath) + + System.IO.Path.DirectorySeparatorChar; + + if (!resolvedCandidate.StartsWith(resolvedBase, StringComparison.OrdinalIgnoreCase)) + { + SendResponse(context, 403, "Forbidden"); + return; + } + + // Missing static asset → fall back to index.html (SPA deep-link support) + if (!File.Exists(resolvedCandidate) && System.IO.Path.HasExtension(requestedPath ?? string.Empty)) + { + resolvedCandidate = System.IO.Path.Combine(htmlDebugPath, "index.html"); + Debug.LogMessage(LogEventLevel.Debug, + "ServeDebugAppRequestHandler: '{requestedPath:l}' not found, falling back to index.html", + requestedPath); + } + + if (!File.Exists(resolvedCandidate)) + { + SendResponse(context, 404, "Not Found"); + return; + } + + var ext = System.IO.Path.GetExtension(resolvedCandidate); + var contentType = MimeTypes.TryGetValue(ext, out var mime) ? mime : "application/octet-stream"; + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = contentType; + + if (TextExtensions.Contains(ext)) + { + string content; + using (var reader = new StreamReader(resolvedCandidate, Encoding.UTF8)) + content = reader.ReadToEnd(); + + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.Write(content, false); + } + else + { + var bytes = System.IO.File.ReadAllBytes(resolvedCandidate); + context.Response.OutputStream.Write(bytes, 0, bytes.Length); + } + + context.Response.End(); } - else + catch (Exception ex) { - var relativePart = requestedPath.Replace('/', System.IO.Path.DirectorySeparatorChar); - candidate = System.IO.Path.Combine(htmlDebugPath, relativePart); + Debug.LogMessage(LogEventLevel.Error, ex, + "ServeDebugAppRequestHandler: Unhandled error serving '{rawUrl:l}'", + context.Request.RawUrl); + try { SendResponse(context, 500, "Internal Server Error"); } + catch { /* best-effort */ } } - - // Resolve to an absolute path and guard against path-traversal attacks - var resolvedCandidate = System.IO.Path.GetFullPath(candidate); - var resolvedBase = System.IO.Path.GetFullPath(htmlDebugPath) - + System.IO.Path.DirectorySeparatorChar; - - if (!resolvedCandidate.StartsWith(resolvedBase, StringComparison.OrdinalIgnoreCase)) - { - SendResponse(context, 403, "Forbidden"); - return; - } - - // Missing static asset → fall back to index.html (SPA deep-link support) - if (!File.Exists(resolvedCandidate) && System.IO.Path.HasExtension(requestedPath ?? string.Empty)) - { - resolvedCandidate = System.IO.Path.Combine(htmlDebugPath, "index.html"); - Debug.LogMessage(LogEventLevel.Debug, - "ServeDebugAppRequestHandler: '{requestedPath:l}' not found, falling back to index.html", - requestedPath); - } - - if (!File.Exists(resolvedCandidate)) - { - SendResponse(context, 404, "Not Found"); - return; - } - - var ext = System.IO.Path.GetExtension(resolvedCandidate); - var contentType = MimeTypes.TryGetValue(ext, out var mime) ? mime : "application/octet-stream"; - - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = contentType; - - if (TextExtensions.Contains(ext)) - { - string content; - using (var reader = new StreamReader(resolvedCandidate)) - content = reader.ReadToEnd(); - - context.Response.ContentEncoding = Encoding.UTF8; - context.Response.Write(content, false); - } - else - { - var bytes = System.IO.File.ReadAllBytes(resolvedCandidate); - context.Response.OutputStream.Write(bytes, 0, bytes.Length); - } - - context.Response.End(); } - catch (Exception ex) + + /// + /// Resolves the absolute path of the /HTML/debug folder on the processor. + /// + /// + /// Global.FilePathPrefix is always {root}/user/programX/ (or + /// equivalent), so walking up two parents gives the processor root that + /// contains the html folder. This mirrors the two-hop strategy used + /// by AssetLoader.ExtractDevToolsZip so that serving and extraction + /// always resolve to the same directory. + /// + private static string GetHtmlDebugPath() { - Debug.LogMessage(LogEventLevel.Error, - "ServeDebugAppRequestHandler: Unhandled error serving '{rawUrl:l}': {ex}", - context.Request.RawUrl, ex.Message); - try { SendResponse(context, 500, "Internal Server Error"); } catch { /* best-effort */ } - } - } - - /// - /// Resolves the absolute path of the /HTML/debug folder on the processor. - /// - /// - /// On a 4-series appliance, Global.FilePathPrefix is - /// {root}/user/programX/, so walking up two parents gives the - /// processor root that contains the html folder. - /// On Virtual Control, Global.FilePathPrefix is {root}/User/, - /// so only one parent hop is needed. - /// - private static string GetHtmlDebugPath() - { - try - { - var separators = new[] { System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }; - var programDir = new DirectoryInfo(Global.FilePathPrefix.TrimEnd(separators)); - - DirectoryInfo rootDir; - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) + try { - // Virtual Control: {root}/User/ → one parent up = {root} - rootDir = programDir.Parent; + var separators = new[] { System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }; + var programDir = new DirectoryInfo(Global.FilePathPrefix.TrimEnd(separators)); + + // Walk up two levels: {root}/user/programX/ → {root}/user/ → {root} + // This matches the path calculation used in AssetLoader.ExtractDevToolsZip. + var userOrNvramDir = programDir.Parent; + var rootDir = userOrNvramDir?.Parent; + + if (rootDir == null) + { + Debug.LogMessage(LogEventLevel.Error, + "ServeDebugAppRequestHandler: Cannot resolve HTML root from FilePathPrefix '{prefix:l}'", + Global.FilePathPrefix); + return null; + } + + return System.IO.Path.Combine(rootDir.FullName, "html", "debug"); } - else + catch (Exception ex) { - // 4-series appliance: {root}/user/programX/ → two parents up = {root} - rootDir = programDir.Parent?.Parent; - } - - if (rootDir == null) - { - Debug.LogMessage(LogEventLevel.Error, - "ServeDebugAppRequestHandler: Cannot resolve HTML root from FilePathPrefix '{prefix:l}'", - Global.FilePathPrefix); + Debug.LogMessage(LogEventLevel.Error, ex, + "ServeDebugAppRequestHandler: Error resolving HTML debug path"); return null; } - - return System.IO.Path.Combine(rootDir.FullName, "html", "debug"); } - catch (Exception ex) + + /// + /// Extracts the file sub-path from the request by parsing RawUrl. + /// Returns an empty string when the URL ends at /debug (root hit). + /// + private static string GetRequestedFilePath(HttpCwsContext context) { - Debug.LogMessage(LogEventLevel.Error, - "ServeDebugAppRequestHandler: Error resolving HTML debug path: {ex}", ex.Message); - return null; + var rawUrl = context.Request.RawUrl ?? string.Empty; + + // Locate the /debug segment in the URL + const string debugToken = "/debug"; + var idx = rawUrl.IndexOf(debugToken, StringComparison.OrdinalIgnoreCase); + if (idx < 0) + return string.Empty; + + var afterDebug = rawUrl.Substring(idx + debugToken.Length); + + // Strip query string + var qIdx = afterDebug.IndexOf('?'); + if (qIdx >= 0) + afterDebug = afterDebug.Substring(0, qIdx); + + // Strip leading slash to get a relative file path + return afterDebug.TrimStart('/'); + } + + private static void SendResponse(HttpCwsContext context, int statusCode, string statusDescription) + { + context.Response.StatusCode = statusCode; + context.Response.StatusDescription = statusDescription; + context.Response.End(); } } - - /// - /// Extracts the file sub-path from the request by parsing RawUrl. - /// Returns an empty string when the URL ends at /debug (root hit). - /// - private static string GetRequestedFilePath(HttpCwsContext context) - { - var rawUrl = context.Request.RawUrl ?? string.Empty; - - // Locate the /debug segment in the URL - const string debugToken = "/debug"; - var idx = rawUrl.IndexOf(debugToken, StringComparison.OrdinalIgnoreCase); - if (idx < 0) - return string.Empty; - - var afterDebug = rawUrl.Substring(idx + debugToken.Length); - - // Strip query string - var qIdx = afterDebug.IndexOf('?'); - if (qIdx >= 0) - afterDebug = afterDebug.Substring(0, qIdx); - - // Strip leading slash to get a relative file path - return afterDebug.TrimStart('/'); - } - - private static void SendResponse(HttpCwsContext context, int statusCode, string statusDescription) - { - context.Response.StatusCode = statusCode; - context.Response.StatusDescription = statusDescription; - context.Response.End(); - } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs index bb0525d9..dd4f416f 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/SetDeviceStreamDebugRequestHandler.cs @@ -4,230 +4,228 @@ using Newtonsoft.Json; using PepperDash.Core; using PepperDash.Core.Web.RequestHandlers; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests for setting stream debugging on devices that support it. -/// The request is expected to be a POST with a JSON body that includes the device key, the desired debug setting, and an optional timeout in minutes. -/// The handler will attempt to find the device by key, cast it to IStreamDebugging, and then set the debugging according to the provided setting and timeout. -/// If any step fails, an appropriate HTTP error response is returned. -/// -public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { /// - /// Handles CONNECT method requests + /// Represents a SetDeviceStreamDebugRequestHandler /// - /// - protected override void HandleConnect(HttpCwsContext context) + public class SetDeviceStreamDebugRequestHandler : WebApiBaseRequestHandler { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles DELETE method requests - /// - /// - protected override void HandleDelete(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles HEAD method requests - /// - /// - protected override void HandleHead(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles OPTIONS method requests - /// - /// - protected override void HandleOptions(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles PATCH method requests - /// - /// - protected override void HandlePatch(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles POST method requests - /// - /// - protected override void HandlePost(HttpCwsContext context) - { - if (context.Request.ContentLength < 0) + /// + /// Handles CONNECT method requests + /// + /// + protected override void HandleConnect(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - var data = context.Request.GetRequestBody(); - if (data == null) + /// + /// Handles DELETE method requests + /// + /// + protected override void HandleDelete(HttpCwsContext context) { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - var config = new SetDeviceStreamDebugConfig(); - var body = JsonConvert.DeserializeAnonymousType(data, config); - if (body == null) + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) { - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - if (string.IsNullOrEmpty(body.DeviceKey) || string.IsNullOrEmpty(body.Setting)) + /// + /// Handles HEAD method requests + /// + /// + protected override void HandleHead(HttpCwsContext context) { - context.Response.StatusCode = 400; - context.Response.StatusDescription = "Bad Request"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device)) + /// + /// Handles OPTIONS method requests + /// + /// + protected override void HandleOptions(HttpCwsContext context) { - context.Response.StatusCode = 404; - context.Response.StatusDescription = "Not Found"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - eStreamDebuggingSetting debugSetting; - try + /// + /// Handles PATCH method requests + /// + /// + protected override void HandlePatch(HttpCwsContext context) { - debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true); - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Exception handling set debug request"); - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); - - return; } - try + /// + /// Handles POST method requests + /// + /// + protected override void HandlePost(HttpCwsContext context) { - var mins = Convert.ToUInt32(body.Timeout); - if (mins > 0) + if (context.Request.ContentLength < 0) { - device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, mins); - } - else - { - device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; } - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.End(); + var data = context.Request.GetRequestBody(); + if (data == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + var config = new SetDeviceStreamDebugConfig(); + var body = JsonConvert.DeserializeAnonymousType(data, config); + if (body == null) + { + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + if (string.IsNullOrEmpty(body.DeviceKey) || string.IsNullOrEmpty(body.Setting)) + { + context.Response.StatusCode = 400; + context.Response.StatusDescription = "Bad Request"; + context.Response.End(); + + return; + } + + if (!(DeviceManager.GetDeviceForKey(body.DeviceKey) is IStreamDebugging device)) + { + context.Response.StatusCode = 404; + context.Response.StatusDescription = "Not Found"; + context.Response.End(); + + return; + } + + eStreamDebuggingSetting debugSetting; + try + { + debugSetting = (eStreamDebuggingSetting)Enum.Parse(typeof(eStreamDebuggingSetting), body.Setting, true); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Exception handling set debug request"); + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + + return; + } + + try + { + var mins = Convert.ToUInt32(body.Timeout); + if (mins > 0) + { + device.StreamDebugging.SetDebuggingWithSpecificTimeout(debugSetting, mins); + } + else + { + device.StreamDebugging.SetDebuggingWithDefaultTimeout(debugSetting); + } + + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.End(); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Exception handling set debug request"); + context.Response.StatusCode = 500; + context.Response.StatusDescription = "Internal Server Error"; + context.Response.End(); + } } - catch (Exception ex) + + /// + /// Handles PUT method requests + /// + /// + protected override void HandlePut(HttpCwsContext context) { - Debug.LogMessage(ex, "Exception handling set debug request"); - context.Response.StatusCode = 500; - context.Response.StatusDescription = "Internal Server Error"; + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected override void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; context.Response.End(); } } + /// - /// Handles PUT method requests + /// Configuration class for SetDeviceStreamDebugRequestHandler /// - /// - protected override void HandlePut(HttpCwsContext context) + public class SetDeviceStreamDebugConfig { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } + /// + /// Gets or sets the DeviceKey + /// + [JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)] + public string DeviceKey { get; set; } - /// - /// Handles TRACE method requests - /// - /// - protected override void HandleTrace(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } -} + /// + /// Gets or sets the Setting + /// + [JsonProperty("setting", NullValueHandling = NullValueHandling.Include)] + public string Setting { get; set; } + /// + /// Gets or sets the Timeout + /// + [JsonProperty("timeout")] + public int Timeout { get; set; } -/// -/// Configuration class for SetDeviceStreamDebugRequestHandler -/// -public class SetDeviceStreamDebugConfig -{ - /// - /// Gets or sets the DeviceKey - /// - [JsonProperty("deviceKey", NullValueHandling = NullValueHandling.Include)] - public string DeviceKey { get; set; } - - /// - /// Gets or sets the Setting - /// - [JsonProperty("setting", NullValueHandling = NullValueHandling.Include)] - public string Setting { get; set; } - - /// - /// Gets or sets the Timeout - /// - [JsonProperty("timeout")] - public int Timeout { get; set; } - - /// - /// Constructor - /// - public SetDeviceStreamDebugConfig() - { - DeviceKey = null; - Setting = null; - Timeout = 15; + /// + /// Constructor + /// + public SetDeviceStreamDebugConfig() + { + DeviceKey = null; + Setting = null; + Timeout = 15; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ShowConfigRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ShowConfigRequestHandler.cs index e053ccce..4964822e 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/ShowConfigRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/ShowConfigRequestHandler.cs @@ -3,39 +3,38 @@ using Newtonsoft.Json; using PepperDash.Core.Web.RequestHandlers; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials.Core.Web.RequestHandlers; - -/// -/// Handles requests to show the current config. -/// This is used to display the current config via the web API. -/// The config is returned as a JSON string. -/// -public class ShowConfigRequestHandler : WebApiBaseRequestHandler +namespace PepperDash.Essentials.Core.Web.RequestHandlers { - /// - /// Constructor - /// - /// - /// base(true) enables CORS support by default - /// - public ShowConfigRequestHandler() - : base(true) + /// + /// Represents a ShowConfigRequestHandler + /// + public class ShowConfigRequestHandler : WebApiBaseRequestHandler { - } + /// + /// Constructor + /// + /// + /// base(true) enables CORS support by default + /// + public ShowConfigRequestHandler() + : base(true) + { + } - /// - /// Handles GET method requests - /// - /// - protected override void HandleGet(HttpCwsContext context) - { - var config = JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented); + /// + /// Handles GET method requests + /// + /// + protected override void HandleGet(HttpCwsContext context) + { + var config = JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented); - context.Response.StatusCode = 200; - context.Response.StatusDescription = "OK"; - context.Response.ContentType = "application/json"; - context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(config, false); - context.Response.End(); + context.Response.StatusCode = 200; + context.Response.StatusDescription = "OK"; + context.Response.ContentType = "application/json"; + context.Response.ContentEncoding = System.Text.Encoding.UTF8; + context.Response.Write(config, false); + context.Response.End(); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Core/Web/RoutingFeedbackWebsocket.cs b/src/PepperDash.Essentials.Core/Web/RoutingFeedbackWebsocket.cs deleted file mode 100644 index 6cdf1e8e..00000000 --- a/src/PepperDash.Essentials.Core/Web/RoutingFeedbackWebsocket.cs +++ /dev/null @@ -1,517 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Timers; -using Crestron.SimplSharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using PepperDash.Core; -using Serilog.Events; -using WebSocketSharp; -using WebSocketSharp.Server; - -namespace PepperDash.Essentials.Core.Web; - -/// -/// WebSocket service that broadcasts real-time routing state changes to connected clients. -/// Subscribes to route-changed events on midpoint and sink devices and pushes updates -/// to all connected WebSocket clients. -/// -public class RoutingFeedbackWebsocket : IKeyed -{ - private HttpServer _httpsServer; - private readonly string _path = "/routing/join/"; - private const string _certificateName = "selfCres"; - private const string _certificatePassword = "cres12345"; - private const long DEBOUNCE_MS = 200; - - private static string CertPath => - $"{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}{_certificateName}.pfx"; - - private readonly Dictionary _debounceTimers = new Dictionary(); - - // Tile-sink children of an IRoutingSinkWithLayouts device (e.g. a multiview decoder's per-window - // sinks) are reported to clients as synthesized inputs on the parent's node, rather than as their - // own separate nodes - see RoutingGraphHelpers. Rebuilt whenever the server (re)starts. - private Dictionary _tileChildren = new Dictionary(); - - private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - NullValueHandling = NullValueHandling.Ignore - }; - - /// - public string Key => "RoutingFeedbackWebsocket"; - - /// - /// Gets the port number on which the server is currently running. - /// - public int Port => _httpsServer?.Port ?? 0; - - /// - /// Gets the WebSocket URL for the current server instance. - /// - public string Url - { - get - { - if (_httpsServer == null || !_httpsServer.IsListening) return ""; - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return ""; - - var ip = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0); - if (string.IsNullOrEmpty(ip) || ip == "Invalid Value") - ip = CrestronEthernetHelper.GetEthernetParameter( - CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 1); - - return $"wss://{ip}:{_httpsServer.Port}{service.Path}"; - } - } - - /// - /// Gets a value indicating whether the server is currently listening. - /// - public bool IsRunning => _httpsServer?.IsListening ?? false; - - /// - /// Gets a value indicating whether there are active WebSocket connections. - /// - public bool HasActiveConnections - { - get - { - if (_httpsServer == null || !_httpsServer.IsListening) return false; - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return false; - return service.Sessions.Count > 0; - } - } - - /// - /// Starts the WebSocket server on the specified port and subscribes to routing events. - /// - /// The port to listen on. - public void StartServerAndSetPort(int port) - { - if (IsRunning) - { - Debug.LogMessage(LogEventLevel.Information, "Routing feedback WebSocket already running on port {port}", this, Port); - return; - } - - Debug.LogMessage(LogEventLevel.Information, "Starting Routing Feedback WebSocket on port: {port}", this, port); - - try - { - _httpsServer = new HttpServer(port, true); - - var cert = LoadCert(CertPath, _certificatePassword); - _httpsServer.SslConfiguration.ServerCertificate = cert; - _httpsServer.SslConfiguration.ClientCertificateRequired = false; - _httpsServer.SslConfiguration.CheckCertificateRevocation = false; - _httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; - _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; - - _httpsServer.AddWebSocketService(_path); - _httpsServer.OnGet += HandleHttpGet; - _httpsServer.Log.Level = LogLevel.Warn; - _httpsServer.Start(); - - RoutingFeedbackClient.Owner = this; - SubscribeToRoutingEvents(); - - Debug.LogMessage(LogEventLevel.Information, "Routing Feedback WebSocket ready at {url}", this, Url); - } - catch (Exception ex) - { - Debug.LogError(ex, "Routing Feedback WebSocket failed to start: {message}", this, ex.Message); - _httpsServer = null; - } - } - - /// - /// Stops the WebSocket server and unsubscribes from routing events. - /// - public void StopServer() - { - UnsubscribeFromRoutingEvents(); - - try - { - if (_httpsServer == null || !_httpsServer.IsListening) - return; - - _httpsServer.Log.Output = (d, s) => { }; - _httpsServer.Stop(); - _httpsServer = null; - } - catch (Exception ex) - { - Debug.LogError(ex, "Routing Feedback WebSocket failed to stop: {message}", this, ex.Message); - } - } - - /// - /// Builds and returns the full current routing state snapshot to send to newly connected clients. - /// - internal string GetSnapshotMessage() - { - var midpointRoutes = new Dictionary>(); - var sinkRoutes = new Dictionary>(); - - // Collect midpoint current routes - var midpointDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in midpointDevices) - { - if (device.CurrentRoutes == null || device.CurrentRoutes.Count == 0) - continue; - - midpointRoutes[device.Key] = device.CurrentRoutes - .Where(r => r.InputPort != null) - .Select(r => new MidpointRouteDto - { - InputPortKey = r.InputPort.Key, - OutputPortKey = r.OutputPort?.Key, - SignalType = r.InputPort.Type.ToString() - }) - .ToList(); - } - - // Collect sink current sources directly from each sink's own current-source bookkeeping - // (ICurrentSources, part of IRoutingSinkWithFeedback), which is authoritative regardless of - // whether the route was made via a tie line (ReleaseAndMakeRoute) or a device-specific bulk - // API (e.g. IHasDynamicMultiviewLayout.ApplyDynamicLayout) that never touches - // TieLineCollection/RouteDescriptorCollection at all. - var sinkDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in sinkDevices) - { - if (device.CurrentInputPort == null) - continue; - - var sourceKey = RoutingGraphHelpers.GetCurrentSourceKey(device); - if (string.IsNullOrEmpty(sourceKey)) - continue; - - var deviceKey = device.Key; - var inputPortKey = device.CurrentInputPort.Key; - - if (_tileChildren.TryGetValue(deviceKey, out var tileInfo)) - { - deviceKey = tileInfo.Parent.Key; - inputPortKey = RoutingGraphHelpers.QualifyTilePortKey(tileInfo.TileNumber, inputPortKey); - } - - if (!sinkRoutes.TryGetValue(deviceKey, out var routes)) - { - routes = new List(); - sinkRoutes[deviceKey] = routes; - } - - routes.Add(new SinkRouteDto - { - InputPortKey = inputPortKey, - SourceDeviceKey = sourceKey, - SignalType = device.CurrentInputPort.Type.ToString() - }); - } - - var snapshot = new RoutingSnapshotDto - { - Type = "snapshot", - MidpointRoutes = midpointRoutes, - SinkRoutes = sinkRoutes, - Layouts = RoutingGraphHelpers.BuildMultiviewLayoutSnapshot() - }; - - return JsonConvert.SerializeObject(snapshot, JsonSettings); - } - - private void SubscribeToRoutingEvents() - { - _tileChildren = RoutingGraphHelpers.BuildTileChildMap(); - - var midpointDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in midpointDevices) - { - device.RouteChanged += HandleMidpointRouteChanged; - } - - var sinkDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in sinkDevices) - { - device.InputChanged += HandleSinkInputChanged; - } - - var layoutDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in layoutDevices) - { - device.LayoutChanged += HandleLayoutChanged; - } - } - - private void UnsubscribeFromRoutingEvents() - { - var midpointDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in midpointDevices) - { - device.RouteChanged -= HandleMidpointRouteChanged; - } - - var sinkDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in sinkDevices) - { - device.InputChanged -= HandleSinkInputChanged; - } - - var layoutDevices = DeviceManager.AllDevices.OfType(); - foreach (var device in layoutDevices) - { - device.LayoutChanged -= HandleLayoutChanged; - } - } - - private void HandleMidpointRouteChanged(IRoutingMidpointWithFeedback midpoint, RouteSwitchDescriptor newRoute) - { - DebounceBroadcast($"midpoint-{midpoint.Key}", () => - { - var routes = midpoint.CurrentRoutes? - .Where(r => r.InputPort != null) - .Select(r => new MidpointRouteDto - { - InputPortKey = r.InputPort.Key, - OutputPortKey = r.OutputPort?.Key, - SignalType = r.InputPort.Type.ToString() - }) - .ToList() ?? new List(); - - var msg = new MidpointRouteChangedDto - { - Type = "midpointRouteChanged", - DeviceKey = midpoint.Key, - Routes = routes - }; - - Broadcast(JsonConvert.SerializeObject(msg, JsonSettings)); - }); - } - - private void HandleLayoutChanged(object sender, MultiviewLayoutStateEventArgs e) - { - if (sender is not IKeyed device) - return; - - DebounceBroadcast($"layout-{device.Key}", () => - { - var msg = new LayoutChangedDto - { - Type = "layoutChanged", - DeviceKey = device.Key, - Layout = e.CurrentLayout - }; - - Broadcast(JsonConvert.SerializeObject(msg, JsonSettings)); - }); - } - - private void HandleSinkInputChanged(IRoutingSinkWithFeedback sender, RoutingInputPort currentInputPort) - { - // Tile-sink children are reported under their IRoutingSinkWithLayouts parent's key, with a - // qualified port key, so clients see this as an input change on the parent's node rather than - // on a device that isn't otherwise represented in the graph. - var deviceKey = sender.Key; - var inputPortKey = currentInputPort?.Key ?? ""; - - if (_tileChildren.TryGetValue(deviceKey, out var tileInfo)) - { - deviceKey = tileInfo.Parent.Key; - if (!string.IsNullOrEmpty(inputPortKey)) - inputPortKey = RoutingGraphHelpers.QualifyTilePortKey(tileInfo.TileNumber, inputPortKey); - } - - DebounceBroadcast($"sink-{sender.Key}", () => - { - // Read the source directly from the sink's own current-source bookkeeping (see - // GetCurrentSourceKey) rather than tracing a tie line - a route made via a - // device-specific bulk API (e.g. ApplyDynamicLayout) never creates a tie line at all. - var sourceDeviceKey = currentInputPort != null ? (RoutingGraphHelpers.GetCurrentSourceKey(sender) ?? "") : ""; - - var msg = new SinkInputChangedDto - { - Type = "sinkInputChanged", - DeviceKey = deviceKey, - InputPortKey = inputPortKey, - SourceDeviceKey = sourceDeviceKey, - SignalType = currentInputPort?.Type.ToString() ?? "" - }; - - Broadcast(JsonConvert.SerializeObject(msg, JsonSettings)); - }); - } - - private void DebounceBroadcast(string key, Action action) - { - lock (_debounceTimers) - { - if (_debounceTimers.TryGetValue(key, out var existingTimer)) - { - existingTimer.Stop(); - existingTimer.Dispose(); - } - - var timer = new Timer(DEBOUNCE_MS) { AutoReset = false }; - timer.Elapsed += (s, e) => - { - try - { - action(); - } - catch (Exception ex) - { - Debug.LogError(ex, "Error in debounced routing broadcast for {key}: {message}", this, key, ex.Message); - } - finally - { - lock (_debounceTimers) - { - if (_debounceTimers.ContainsKey(key)) - { - _debounceTimers[key]?.Dispose(); - _debounceTimers.Remove(key); - } - } - } - }; - timer.Start(); - _debounceTimers[key] = timer; - } - } - - private void Broadcast(string message) - { - if (_httpsServer == null || !_httpsServer.IsListening) return; - - var service = _httpsServer.WebSocketServices[_path]; - if (service == null) return; - - service.Sessions.Broadcast(message); - } - - private void HandleHttpGet(object sender, HttpRequestEventArgs e) - { - var res = e.Response; - var body = System.Text.Encoding.UTF8.GetBytes( - "

Certificate accepted.

You can close this tab and return to the application.

"); - res.ContentType = "text/html"; - res.ContentLength64 = body.Length; - res.Close(body, true); - } - - private static X509Certificate2 LoadCert(string certPath, string certPassword) - { - return new X509Certificate2(certPath, certPassword, X509KeyStorageFlags.EphemeralKeySet); - } - - // ── DTOs ───────────────────────────────────────────────────────────────── - - private class RoutingSnapshotDto - { - public string Type { get; set; } - public Dictionary> MidpointRoutes { get; set; } - public Dictionary> SinkRoutes { get; set; } - public Dictionary Layouts { get; set; } - } - - private class LayoutChangedDto - { - public string Type { get; set; } - public string DeviceKey { get; set; } - public MultiviewLayoutState Layout { get; set; } - } - - private class MidpointRouteChangedDto - { - public string Type { get; set; } - public string DeviceKey { get; set; } - public List Routes { get; set; } - } - - private class SinkInputChangedDto - { - public string Type { get; set; } - public string DeviceKey { get; set; } - public string InputPortKey { get; set; } - public string SourceDeviceKey { get; set; } - public string SignalType { get; set; } - } - - private class MidpointRouteDto - { - public string InputPortKey { get; set; } - public string OutputPortKey { get; set; } - public string SignalType { get; set; } - } - - private class SinkRouteDto - { - public string InputPortKey { get; set; } - public string SourceDeviceKey { get; set; } - public string SignalType { get; set; } - } -} - -/// -/// WebSocket client behavior for routing feedback connections. -/// Sends a full state snapshot on connect. -/// -public class RoutingFeedbackClient : WebSocketBehavior -{ - /// - /// Static reference to the owning instance. - /// Set before the server starts accepting connections. - /// - internal static RoutingFeedbackWebsocket Owner { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public RoutingFeedbackClient() - { - } - - /// - protected override void OnOpen() - { - base.OnOpen(); - Debug.LogMessage(LogEventLevel.Information, "Routing feedback client connected from: {url}", Owner, Context.WebSocket.Url); - - // Send full state snapshot to the newly connected client - try - { - var snapshot = Owner.GetSnapshotMessage(); - Send(snapshot); - } - catch (Exception ex) - { - Debug.LogError(ex, "Error sending routing snapshot to client: {message}", Owner, ex.Message); - } - } - - /// - protected override void OnClose(CloseEventArgs e) - { - base.OnClose(e); - Debug.LogMessage(LogEventLevel.Debug, "Routing feedback client disconnected: {code} {reason}", Owner, e.Code, e.Reason); - } - - /// - protected override void OnError(WebSocketSharp.ErrorEventArgs e) - { - base.OnError(e); - Debug.LogError(e.Exception, "Routing feedback client error: {message}", Owner, e.Message); - } -} diff --git a/src/PepperDash.Essentials.Core/Web/RoutingGraphHelpers.cs b/src/PepperDash.Essentials.Core/Web/RoutingGraphHelpers.cs deleted file mode 100644 index 387c8824..00000000 --- a/src/PepperDash.Essentials.Core/Web/RoutingGraphHelpers.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using PepperDash.Core; - -namespace PepperDash.Essentials.Core.Web; - -/// -/// Shared helpers for building routing-graph data for consumers of the routing dev tools -/// ( and -/// ). -/// -/// -/// Devices that implement (e.g. a multiview decoder) expose a -/// set of per-tile child sink devices (), each of -/// which is independently registered with and independently routable. -/// Rendered naively, each tile shows up as its own top-level node in the routing graph, which is -/// confusing - a multiview decoder with N tiles doesn't look like N separate devices to a user. -/// These helpers let routing-graph consumers instead: skip tile-sink children when enumerating -/// top-level devices, and represent each tile as a distinctly-keyed input "edge target" on the -/// parent's own node (see ), remapping any tie line / active route -/// that targets a tile so it points at the parent device instead. -/// -public static class RoutingGraphHelpers -{ - /// - /// Describes where a tile-sink child device () - /// belongs, for remapping it back to its parent's node in routing-graph output. - /// - public readonly struct TileChildInfo - { - /// The parent device this tile belongs to. - public IRoutingSinkWithLayouts Parent { get; } - - /// The tile's 1-based window number within the parent's layout. - public int TileNumber { get; } - - /// Initializes a new instance of the struct. - public TileChildInfo(IRoutingSinkWithLayouts parent, int tileNumber) - { - Parent = parent; - TileNumber = tileNumber; - } - } - - /// - /// Builds a map of every tile-sink child device key (across all - /// devices currently in ) to its parent device and tile number. - /// - public static Dictionary BuildTileChildMap() - { - var map = new Dictionary(); - - foreach (var parent in DeviceManager.AllDevices.OfType()) - { - foreach (var kvp in parent.WindowTileSinks) - { - if (kvp.Value is not IKeyed tile || string.IsNullOrEmpty(tile.Key)) - continue; - - map[tile.Key] = new TileChildInfo(parent, kvp.Key); - } - } - - return map; - } - - /// - /// Builds a graph-unique port key for a tile's port, so multiple tiles synthesized onto the same - /// parent node don't collide (every tile's own InputPorts collection typically contains a - /// single, identically-keyed port, e.g. "tileInput"). - /// - public static string QualifyTilePortKey(int tileNumber, string portKey) => $"tile{tileNumber}:{portKey}"; - - /// - /// Gets the device key of whatever source is currently feeding a sink's Video signal (falling back - /// to Audio), read directly from its own bookkeeping (part of - /// ). This is authoritative regardless of whether the route - /// was made via a tie line (ReleaseAndMakeRoute) or a device-specific bulk API (e.g. - /// IHasDynamicMultiviewLayout.ApplyDynamicLayout) that never touches - /// TieLineCollection/RouteDescriptorCollection at all. - /// - public static string GetCurrentSourceKey(IRoutingSinkWithFeedback device) - { - if (device.CurrentSourceKeys.TryGetValue(eRoutingSignalType.Video, out var videoKey) && !string.IsNullOrEmpty(videoKey)) - return videoKey; - - return device.CurrentSourceKeys.TryGetValue(eRoutingSignalType.Audio, out var audioKey) ? audioKey : null; - } - - /// - /// Builds a snapshot of the current for every device in - /// that implements , keyed - /// by device key. Devices with no currently active layout (CurrentLayout == null) are - /// omitted. Shared by (initial - /// HTTP snapshot) and (WebSocket snapshot on connect). - /// - public static Dictionary BuildMultiviewLayoutSnapshot() - { - var result = new Dictionary(); - - foreach (var device in DeviceManager.AllDevices.OfType()) - { - var layout = device.CurrentLayout; - if (layout == null) - continue; - - result[device.Key] = layout; - } - - return result; - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs b/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs index d9106489..c0f8ac1e 100644 --- a/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs +++ b/src/PepperDash.Essentials.Devices.Common/Audio/GenericAudioOut.cs @@ -1,195 +1,157 @@ using System.Collections.Generic; using PepperDash.Core; -using PepperDash.Core.Logging; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; + using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common; - -/// -/// Represents and audio endpoint -/// -public class GenericAudioOut : EssentialsDevice, IRoutingSinkWithFeedback +namespace PepperDash.Essentials.Devices.Common { - /// - public RoutingInputPort CurrentInputPort => AnyAudioIn; - - /// - public event InputChangedEventHandler InputChanged; - - /// - public void ExecuteSwitch(object inputSelector) { } - - /// - public Dictionary CurrentSources { get; private set; } - - /// - public Dictionary CurrentSourceKeys { get; private set; } - - /// - public event System.EventHandler CurrentSourcesChanged; - - /// - /// Gets or sets the AnyAudioIn + /// Represents and audio endpoint /// - public RoutingInputPort AnyAudioIn { get; private set; } - - /// - /// Constructor for GenericAudioOut - /// - /// Device key - /// Device name - public GenericAudioOut(string key, string name) - : base(key, name) + public class GenericAudioOut : EssentialsDevice, IRoutingSink { - AnyAudioIn = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio, - eRoutingPortConnectionType.LineAudio, null, this); + /// + /// Gets the current input port + /// + public RoutingInputPort CurrentInputPort => AnyAudioIn; - CurrentSources = new Dictionary - { - { eRoutingSignalType.Audio, null }, - }; + /// + /// Event fired when the current source changes + /// + public event SourceInfoChangeHandler CurrentSourceChange; - CurrentSourceKeys = new Dictionary + /// + /// Gets or sets the current source info key + /// + public string CurrentSourceInfoKey { get; set; } + /// + /// Gets or sets the current source info + /// + public SourceListItem CurrentSourceInfo + { + get { - { eRoutingSignalType.Audio, string.Empty }, - }; + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSourceChange; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + + /// + /// Gets or sets the AnyAudioIn + /// + public RoutingInputPort AnyAudioIn { get; private set; } + + /// + /// Constructor for GenericAudioOut + /// + /// Device key + /// Device name + public GenericAudioOut(string key, string name) + : base(key, name) + { + AnyAudioIn = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio, + eRoutingPortConnectionType.LineAudio, null, this); + } + + #region IRoutingInputs Members + + /// + /// Gets the collection of input ports + /// + public RoutingPortCollection InputPorts + { + get { return new RoutingPortCollection { AnyAudioIn }; } + } + + #endregion } + + /// + /// Represents a GenericAudioOutWithVolume + /// + public class GenericAudioOutWithVolume : GenericAudioOut, IHasVolumeDevice + { + /// + /// Gets the volume device key + /// + public string VolumeDeviceKey { get; private set; } + /// + /// Gets the volume zone + /// + public uint VolumeZone { get; private set; } + + /// + /// Gets the volume device + /// + public IBasicVolumeControls VolumeDevice + { + get + { + var dev = DeviceManager.GetDeviceForKey(VolumeDeviceKey); + if (dev is IAudioZones) + return (dev as IAudioZones).Zone[VolumeZone]; + else return dev as IBasicVolumeControls; + } + } + + /// + /// Constructor - adds the name to the attached audio device, if appropriate. + /// + /// + /// + /// + /// + public GenericAudioOutWithVolume(string key, string name, string audioDevice, uint zone) + : base(key, name) + { + VolumeDeviceKey = audioDevice; + VolumeZone = zone; + } + + } + + /// + /// Factory for creating GenericAudioOutWithVolume devices + /// + public class GenericAudioOutWithVolumeFactory : EssentialsDeviceFactory + { + /// + /// Constructor for GenericAudioOutWithVolumeFactory + /// + public GenericAudioOutWithVolumeFactory() + { + TypeNames = new List() { "genericaudiooutwithvolume" }; + } + + /// + /// BuildDevice method + /// /// - public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - foreach (eRoutingSignalType type in System.Enum.GetValues(typeof(eRoutingSignalType))) + public override EssentialsDevice BuildDevice(DeviceConfig dc) { - var flagValue = System.Convert.ToInt32(type); - // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). - // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. - if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - if (!signalType.HasFlag(type)) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - this.LogDebug("setting {type}", type); - - CurrentSources.TryGetValue(type, out var previousSource); - - UpdateCurrentSources(type, previousSource, sourceDevice); + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new GenericAudioOutWithVolumeFactory Device"); + var zone = dc.Properties.Value("zone"); + return new GenericAudioOutWithVolume(dc.Key, dc.Name, + dc.Properties.Value("volumeDeviceKey"), zone); } } - private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice) - { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceDevice; - } - else - { - CurrentSources.Add(signalType, sourceDevice); - } - - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) - { - CurrentSourceKeys[signalType] = sourceDevice?.Key; - } - else - { - CurrentSourceKeys.Add(signalType, sourceDevice?.Key); - } - - // Raise the CurrentSourcesChanged event - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - - #region IRoutingInputs Members - - /// - /// Gets the collection of input ports - /// - public RoutingPortCollection InputPorts - { - get { return new RoutingPortCollection { AnyAudioIn }; } - } - - #endregion -} - - -/// -/// Represents a GenericAudioOutWithVolume -/// -public class GenericAudioOutWithVolume : GenericAudioOut, IHasVolumeDevice -{ - /// - /// Gets the volume device key - /// - public string VolumeDeviceKey { get; private set; } - /// - /// Gets the volume zone - /// - public uint VolumeZone { get; private set; } - - /// - /// Gets the volume device - /// - public IBasicVolumeControls VolumeDevice - { - get - { - var dev = DeviceManager.GetDeviceForKey(VolumeDeviceKey); - if (dev is IAudioZones) - return (dev as IAudioZones).Zone[VolumeZone]; - else return dev as IBasicVolumeControls; - } - } - - /// - /// Constructor - adds the name to the attached audio device, if appropriate. - /// - /// - /// - /// - /// - public GenericAudioOutWithVolume(string key, string name, string audioDevice, uint zone) - : base(key, name) - { - VolumeDeviceKey = audioDevice; - VolumeZone = zone; - } - -} - -/// -/// Factory for creating GenericAudioOutWithVolume devices -/// -public class GenericAudioOutWithVolumeFactory : EssentialsDeviceFactory -{ - /// - /// Constructor for GenericAudioOutWithVolumeFactory - /// - public GenericAudioOutWithVolumeFactory() - { - TypeNames = new List() { "genericaudiooutwithvolume" }; - } - - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new GenericAudioOutWithVolumeFactory Device"); - var zone = dc.Properties.Value("zone"); - return new GenericAudioOutWithVolume(dc.Key, dc.Name, - dc.Properties.Value("volumeDeviceKey"), zone); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs b/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs deleted file mode 100644 index e368a521..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System.Collections.Generic; -using System.Timers; - -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using Serilog.Events; - -namespace PepperDash.Essentials.Devices.Common; - -/// -/// Mock audio device for testing and simulation purposes. Behaves like a real DSP fader channel, -/// with ramping volume up/down while pressed and mute on/off/toggle, all backed by fake in-memory -/// state rather than any real hardware communication. -/// -public class MockAudioDevice : EssentialsDevice, IBasicVolumeWithFeedback -{ - private const int VolumeHeldRepeatIntervalMs = 100; - private const ushort VolumeStep = 655; - - private ushort _volumeLevel = 32768; - private bool _isMuted; - private Timer _volumeUpTimer; - private Timer _volumeDownTimer; - - /// - public IntFeedback VolumeLevelFeedback { get; private set; } - - /// - public BoolFeedback MuteFeedback { get; private set; } - - /// - /// Constructor for MockAudioDevice - /// - /// Device key - /// Device name - public MockAudioDevice(string key, string name) - : base(key, name) - { - VolumeLevelFeedback = new IntFeedback("volume", () => _volumeLevel); - MuteFeedback = new BoolFeedback("muteOn", () => _isMuted); - - // Seed the feedback's cached value immediately so the UI reflects the starting level - // rather than 0 until the first volume change fires an update. - VolumeLevelFeedback.FireUpdate(); - MuteFeedback.FireUpdate(); - } - - /// - public void SetVolume(ushort level) - { - _volumeLevel = level; - VolumeLevelFeedback.InvokeFireUpdate(); - this.LogDebug("SetVolume: {Level}", _volumeLevel); - } - - /// - public void MuteOn() - { - _isMuted = true; - MuteFeedback.InvokeFireUpdate(); - } - - /// - public void MuteOff() - { - _isMuted = false; - MuteFeedback.InvokeFireUpdate(); - } - - /// - public void MuteToggle() - { - if (_isMuted) - MuteOff(); - else - MuteOn(); - } - - /// - public void VolumeUp(bool pressRelease) - { - if (pressRelease) - { - RampVolume(VolumeStep); - - if (_volumeUpTimer == null) - { - _volumeUpTimer = new Timer(VolumeHeldRepeatIntervalMs) { AutoReset = true }; - _volumeUpTimer.Elapsed += (s, e) => RampVolume(VolumeStep); - _volumeUpTimer.Start(); - } - } - else - { - _volumeUpTimer?.Stop(); - _volumeUpTimer = null; - } - } - - /// - public void VolumeDown(bool pressRelease) - { - if (pressRelease) - { - RampVolume(-VolumeStep); - - if (_volumeDownTimer == null) - { - _volumeDownTimer = new Timer(VolumeHeldRepeatIntervalMs) { AutoReset = true }; - _volumeDownTimer.Elapsed += (s, e) => RampVolume(-VolumeStep); - _volumeDownTimer.Start(); - } - } - else - { - _volumeDownTimer?.Stop(); - _volumeDownTimer = null; - } - } - - // Clamps to ushort range so repeated ramping at the extremes doesn't wrap around. - private void RampVolume(int delta) - { - var newLevel = _volumeLevel + delta; - if (newLevel < ushort.MinValue) - newLevel = ushort.MinValue; - else if (newLevel > ushort.MaxValue) - newLevel = ushort.MaxValue; - - SetVolume((ushort)newLevel); - } -} - -/// -/// Factory for building devices. -/// -public class MockAudioDeviceFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the class. - /// - public MockAudioDeviceFactory() - { - TypeNames = new List { "mockaudiodevice", "mockaudio" }; - } - - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory attempting to create new Mock Audio Device"); - return new MockAudioDevice(dc.Key, dc.Name); - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/AudioCodecBase.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/AudioCodecBase.cs index 5c0f3885..7f760bc3 100644 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/AudioCodecBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/AudioCodec/AudioCodecBase.cs @@ -4,116 +4,117 @@ using System.Linq; using PepperDash.Essentials.Core; using PepperDash.Essentials.Devices.Common.Codec; -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Base class for audio codecs. Provides common properties and methods for audio codecs, -/// as well as a common implementation of IDialerCallStatus to allow the AudioCodecBaseMessenger -/// to get call status information without requiring the full AudioCodecBase class. -/// This is useful for devices that have dialer call status information but do not need to implement -/// the full AudioCodecBase class. -/// -public abstract class AudioCodecBase : EssentialsDevice, IDialerCallStatus, IUsageTracking, IAudioCodecInfo +namespace PepperDash.Essentials.Devices.Common.AudioCodec { - /// - public event EventHandler CallStatusChange; - - /// - public AudioCodecInfo CodecInfo { get; protected set; } - - #region IUsageTracking Members - /// - /// This object can be added by outside users of this class to provide usage tracking - /// for various services + /// Abstract base class for audio codec devices /// - public UsageTracking UsageTracker { get; set; } - - #endregion - - /// - /// Returns true when any call is not in state Unknown, Disconnecting, Disconnected - /// - public bool IsInCall + public abstract class AudioCodecBase : EssentialsDevice, IHasDialer, IUsageTracking, IAudioCodecInfo { - get + + /// + /// Event fired when call status changes + /// + public event EventHandler CallStatusChange; + + /// + /// Gets or sets the CodecInfo + /// + public AudioCodecInfo CodecInfo { get; protected set; } + + #region IUsageTracking Members + + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } + + #endregion + + /// + /// Returns true when any call is not in state Unknown, Disconnecting, Disconnected + /// + public bool IsInCall { - bool value; + get + { + bool value; - if (ActiveCalls != null) - value = ActiveCalls.Any(c => c.IsActiveCall); - else - value = false; - return value; + if (ActiveCalls != null) + value = ActiveCalls.Any(c => c.IsActiveCall); + else + value = false; + return value; + } } - } - // In most cases only a single call can be active - /// - public List ActiveCalls { get; set; } + // In most cases only a single call can be active + /// + /// Gets or sets the ActiveCalls + /// + public List ActiveCalls { get; set; } - /// - /// Constructor - /// - /// - /// - public AudioCodecBase(string key, string name) - : base(key, name) - { - ActiveCalls = new List(); - } - - /// - /// Helper method to fire CallStatusChange event with old and new status - /// - protected void SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus newStatus, CodecActiveCallItem call) - { - call.Status = newStatus; - - OnCallStatusChange(call); - - } - - /// - /// - /// - /// - /// - /// - protected void OnCallStatusChange(CodecActiveCallItem item) - { - var handler = CallStatusChange; - if (handler != null) - handler(this, new CodecCallStatusItemChangeEventArgs(item)); - - if (UsageTracker != null) + /// + /// Constructor for AudioCodecBase + /// + /// Device key + /// Device name + public AudioCodecBase(string key, string name) + : base(key, name) { - if (IsInCall && !UsageTracker.UsageTrackingStarted) - UsageTracker.StartDeviceUsage(); - else if (UsageTracker.UsageTrackingStarted && !IsInCall) - UsageTracker.EndDeviceUsage(); + ActiveCalls = new List(); } + + /// + /// Helper method to fire CallStatusChange event with old and new status + /// + protected void SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus newStatus, CodecActiveCallItem call) + { + call.Status = newStatus; + + OnCallStatusChange(call); + + } + + /// + /// Handles call status change events + /// + /// The call item that changed status + protected void OnCallStatusChange(CodecActiveCallItem item) + { + var handler = CallStatusChange; + if (handler != null) + handler(this, new CodecCallStatusItemChangeEventArgs(item)); + + if (UsageTracker != null) + { + if (IsInCall && !UsageTracker.UsageTrackingStarted) + UsageTracker.StartDeviceUsage(); + else if (UsageTracker.UsageTrackingStarted && !IsInCall) + UsageTracker.EndDeviceUsage(); + } + } + + #region IHasDialer Members + + /// + public abstract void Dial(string number); + + /// + public abstract void EndCall(CodecActiveCallItem activeCall); + + /// + public abstract void EndAllCalls(); + + /// + public abstract void AcceptCall(CodecActiveCallItem item); + + /// + public abstract void RejectCall(CodecActiveCallItem item); + + /// + public abstract void SendDtmf(string digit); + + #endregion } - - #region IHasDialer Members - - /// - public abstract void Dial(string number); - - /// - public abstract void EndCall(CodecActiveCallItem activeCall); - - /// - public abstract void EndAllCalls(); - - /// - public abstract void AcceptCall(CodecActiveCallItem item); - - - public abstract void RejectCall(CodecActiveCallItem item); - - - public abstract void SendDtmf(string digit); - - #endregion } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecInfo.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecInfo.cs index 6d45515e..3e6b0418 100644 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecInfo.cs +++ b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecInfo.cs @@ -1,18 +1,24 @@ - -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Implements a common set of data about a codec -/// -public interface IAudioCodecInfo +namespace PepperDash.Essentials.Devices.Common.AudioCodec { - AudioCodecInfo CodecInfo { get; } -} + /// + /// Implements a common set of data about a codec + /// + public interface IAudioCodecInfo + { + /// + /// Gets the codec information + /// + AudioCodecInfo CodecInfo { get; } + } -/// -/// Stores general information about a codec -/// -public abstract class AudioCodecInfo -{ - public abstract string PhoneNumber { get; set; } + /// + /// Stores general information about a codec + /// + public abstract class AudioCodecInfo + { + /// + /// Gets or sets the phone number + /// + public abstract string PhoneNumber { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecPhonebook.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecPhonebook.cs deleted file mode 100644 index 3391f462..00000000 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IAudioCodecPhonebook.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Defines the contract for a device that has a phonebook. -/// This is used to provide a common interface for devices that have phonebook functionality -/// -public interface IAudioCodecPhonebook : IHasDialer -{ - /// - /// Raised when the list of phonebook entries changes. The event args contain the updated list of entries. - /// - event EventHandler ListChanged; - - /// - /// Sets a phonebook entry at the specified index. The implementation of this method is up to the device, but it should update the phonebook entry at the specified index with the provided name and number. - /// - /// - /// - /// - void SetPhonebookEntry(int index, string name, string number); - - /// - /// Dials the phonebook entry at the specified index. - /// - /// - void DialPhonebookEntry(int index); - - /// - /// Gets the list of phonebook entries for the device. - /// - List PhonebookEntries { get; } -} - -/// -/// Defines a phonebook entry for the audio codec phonebook. -/// This is used to provide a common data structure for phonebook entries across different devices. -/// -public class CodecPhonebookEntry -{ - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("number")] - public string Number { get; set; } -} - -/// -/// Provides the updated list of phonebook entries for the event. -/// -public class PhonebookListChangedEventArgs : EventArgs -{ - /// - /// Gets the updated list of phonebook entries. - /// - public List Entries { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The updated list of phonebook entries. - public PhonebookListChangedEventArgs(List entries) - { - Entries = entries; - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDialerCallStatus.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDialerCallStatus.cs deleted file mode 100644 index 7c32eecf..00000000 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDialerCallStatus.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Defines the contract for a device that has dialer call status information. This is used to provide a common interface for the AudioCodecBaseMessenger to get call status information without requiring the full AudioCodecBase class -/// -public interface IDialerCallStatus : IHasDialer -{ - /// - /// - /// - AudioCodecInfo CodecInfo { get; } - - /// - /// Gets or sets the list of active calls for the device. - /// - List ActiveCalls { get; set; } -} diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDtmfDecode.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDtmfDecode.cs deleted file mode 100644 index a161a3d1..00000000 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IDtmfDecode.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Interface for devices that can decode DTMF digits -/// -public interface IDtmfDecode -{ - /// - /// Event fired when a DTMF digit is received - /// - event EventHandler DtmfReceived; -} - -/// -/// Event args for DTMF received event -/// -public class DtmfReceivedEventArgs : EventArgs -{ - /// - /// The DTMF digit that was received - /// - public string Digit { get; private set; } - - /// - /// Event args for DTMF received event - /// - /// The DTMF digit that was received - public DtmfReceivedEventArgs(string digit) - { - Digit = digit; - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IHasAudioCodec.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IHasAudioCodec.cs index df62d915..946c2053 100644 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IHasAudioCodec.cs +++ b/src/PepperDash.Essentials.Devices.Common/AudioCodec/Interfaces/IHasAudioCodec.cs @@ -1,14 +1,17 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -/// -/// Interface for devices that have an audio codec. -/// -public interface IHasAudioCodec:IHasInCallFeedback +namespace PepperDash.Essentials.Devices.Common.AudioCodec { /// - /// Gets the audio codec device + /// For rooms that have audio codec /// - AudioCodecBase AudioCodec { get; } + public interface IHasAudioCodec : IHasInCallFeedback + { + /// + /// Gets the audio codec device + /// + AudioCodecBase AudioCodec { get; } + + //List ActiveCalls { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAC.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAC.cs index 6749abf1..48235a45 100644 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAC.cs +++ b/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAC.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using PepperDash.Core; using PepperDash.Essentials.Core; @@ -7,165 +6,169 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Devices.Common.Codec; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -public class MockAC : AudioCodecBase, IAudioCodecPhonebook +namespace PepperDash.Essentials.Devices.Common.AudioCodec { - public event EventHandler ListChanged; - - public List PhonebookEntries { get; } - - private int maxCalls = 2; - - public MockAC(string key, string name, MockAcPropertiesConfig props) - : base(key, name) + /// + /// Represents a MockAC + /// + public class MockAC : AudioCodecBase { - CodecInfo = new MockAudioCodecInfo(); - - CodecInfo.PhoneNumber = props.PhoneNumber; - - PhonebookEntries = new List + /// + /// Constructor for MockAC + /// + /// Device key + /// Device name + /// MockAC properties configuration + public MockAC(string key, string name, MockAcPropertiesConfig props) + : base(key, name) { - new() { Name = "Judge Chambers", Number = "5551001" }, - new() { Name = "Clerk Office", Number = "5551002" }, - new() { Name = "Court Reporter", Number = "5551003" }, - new() { Name = "Jury Room", Number = "5551004" }, - new() { Name = "Witness Room", Number = "5551005" }, - new() { Name = "Prosecution", Number = "5551006" }, - new() { Name = "Defense Counsel", Number = "5551007" }, - new() { Name = "Bailiff Station", Number = "5551008" }, - new() { Name = "Conference Room A", Number = "5551009" }, - new() { Name = "Conference Room B", Number = "5551010" }, - }; - } + CodecInfo = new MockAudioCodecInfo(); + CodecInfo.PhoneNumber = props.PhoneNumber; + } - public override void Dial(string number) - { - if (ActiveCalls.Count < maxCalls) + /// + /// Dial method + /// + /// + public override void Dial(string number) { - Debug.LogMessage(LogEventLevel.Debug, this, "Dial: {0}", number); - var call = new CodecActiveCallItem() + if (!IsInCall) { - Name = "Mock Outgoing Call", - Number = number, - Type = eCodecCallType.Audio, - Status = eCodecCallStatus.Connected, - Direction = eCodecCallDirection.Outgoing, - Id = "mockAudioCall-1" - }; + Debug.LogMessage(LogEventLevel.Debug, this, "Dial: {0}", number); + var call = new CodecActiveCallItem() + { + Name = "Mock Outgoing Call", + Number = number, + Type = eCodecCallType.Audio, + Status = eCodecCallStatus.Connected, + Direction = eCodecCallDirection.Outgoing, + Id = "mockAudioCall-1" + }; - ActiveCalls.Add(call); + ActiveCalls.Add(call); - OnCallStatusChange(call); + OnCallStatusChange(call); + } + else + { + Debug.LogMessage(LogEventLevel.Debug, this, "Already in call. Cannot dial new call."); + } } - else - { - Debug.LogMessage(LogEventLevel.Debug, this, "Already in call. Cannot dial new call."); - } - } - public override void EndCall(CodecActiveCallItem call) - { - Debug.LogMessage(LogEventLevel.Debug, this, "EndCall"); - ActiveCalls.Remove(call); - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); - } - - public override void EndAllCalls() - { - Debug.LogMessage(LogEventLevel.Debug, this, "EndAllCalls"); - for (int i = ActiveCalls.Count - 1; i >= 0; i--) + /// + /// EndCall method + /// + /// + public override void EndCall(CodecActiveCallItem call) { - var call = ActiveCalls[i]; + Debug.LogMessage(LogEventLevel.Debug, this, "EndCall"); ActiveCalls.Remove(call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); } - } - public override void AcceptCall(CodecActiveCallItem call) - { - Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall"); - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); - } - - public override void RejectCall(CodecActiveCallItem call) - { - Debug.LogMessage(LogEventLevel.Debug, this, "RejectCall"); - ActiveCalls.Remove(call); - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); - } - - public override void SendDtmf(string s) - { - Debug.LogMessage(LogEventLevel.Debug, this, "BEEP BOOP SendDTMF: {0}", s); - } - - public void SetPhonebookEntry(int index, string name, string number) - { - if (index < 0 || index >= PhonebookEntries.Count) + /// + /// EndAllCalls method + /// + /// + public override void EndAllCalls() { - Debug.LogMessage(LogEventLevel.Debug, this, "SetPhonebookEntry: index {0} out of range", index); - return; + Debug.LogMessage(LogEventLevel.Debug, this, "EndAllCalls"); + for (int i = ActiveCalls.Count - 1; i >= 0; i--) + { + var call = ActiveCalls[i]; + ActiveCalls.Remove(call); + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); + } } - PhonebookEntries[index] = new CodecPhonebookEntry { Name = name, Number = number }; - ListChanged?.Invoke(this, new PhonebookListChangedEventArgs(PhonebookEntries)); - } - - public void DialPhonebookEntry(int index) - { - if (index < 0 || index >= PhonebookEntries.Count) + /// + /// AcceptCall method + /// + /// + public override void AcceptCall(CodecActiveCallItem call) { - Debug.LogMessage(LogEventLevel.Debug, this, "DialPhonebookEntry: index {0} out of range", index); - return; + Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall"); + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); + } + + /// + /// RejectCall method + /// + public override void RejectCall(CodecActiveCallItem call) + { + Debug.LogMessage(LogEventLevel.Debug, this, "RejectCall"); + ActiveCalls.Remove(call); + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Disconnected, call); + } + + /// + /// SendDtmf method + /// + /// + public override void SendDtmf(string s) + { + Debug.LogMessage(LogEventLevel.Debug, this, "BEEP BOOP SendDTMF: {0}", s); + } + + /// + /// TestIncomingAudioCall method + /// + /// Phone number to call from + public void TestIncomingAudioCall(string number) + { + Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingAudioCall from {0}", number); + var call = new CodecActiveCallItem() { Name = number, Id = number, Number = number, Type = eCodecCallType.Audio, Direction = eCodecCallDirection.Incoming }; + ActiveCalls.Add(call); + SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Ringing, call); } - Dial(PhonebookEntries[index].Number); } /// - /// + /// Represents a MockAudioCodecInfo /// - /// - public void TestIncomingAudioCall(string number) + public class MockAudioCodecInfo : AudioCodecInfo { - Debug.LogMessage(LogEventLevel.Debug, this, "TestIncomingAudioCall from {0}", number); - var call = new CodecActiveCallItem() { Name = number, Id = number, Number = number, Type = eCodecCallType.Audio, Direction = eCodecCallDirection.Incoming }; - ActiveCalls.Add(call); - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Ringing, call); - } + string _phoneNumber; -} - -public class MockAudioCodecInfo : AudioCodecInfo -{ - string _phoneNumber; - - public override string PhoneNumber - { - get + /// + public override string PhoneNumber { - return _phoneNumber; - } - set - { - _phoneNumber = value; + get + { + return _phoneNumber; + } + set + { + _phoneNumber = value; + } } } -} -public class MockACFactory : EssentialsDeviceFactory -{ - public MockACFactory() + /// + /// Represents a MockACFactory + /// + public class MockACFactory : EssentialsDeviceFactory { - TypeNames = new List() { "mockac" }; + /// + /// Constructor for MockACFactory + /// + public MockACFactory() + { + TypeNames = new List() { "mockac" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MockAc Device"); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); + return new AudioCodec.MockAC(dc.Key, dc.Name, props); + } } - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MockAc Device"); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - return new AudioCodec.MockAC(dc.Key, dc.Name, props); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAcPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAcPropertiesConfig.cs index 7ac205bb..0deaca97 100644 --- a/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAcPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/AudioCodec/MockAC/MockAcPropertiesConfig.cs @@ -1,9 +1,16 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials.Devices.Common.AudioCodec; - -public class MockAcPropertiesConfig +namespace PepperDash.Essentials.Devices.Common.AudioCodec { - [JsonProperty("phoneNumber")] - public string PhoneNumber { get; set; } + /// + /// Represents a MockAcPropertiesConfig + /// + public class MockAcPropertiesConfig + { + /// + /// Gets or sets the PhoneNumber + /// + [JsonProperty("phoneNumber")] + public string PhoneNumber { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs index 3ee7b7be..e2e15668 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraBase.cs @@ -1,4 +1,6 @@ -using System; + + +using System; using System.Collections.Generic; using System.Linq; using Crestron.SimplSharpPro.DeviceSupport; @@ -11,266 +13,337 @@ using PepperDash.Essentials.Core.Devices; using PepperDash.Essentials.Core.Presets; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Cameras; - -public enum eCameraCapabilities +namespace PepperDash.Essentials.Devices.Common.Cameras { - None = 0, - Pan = 1, - Tilt = 2, - Zoom = 4, - Focus = 8 -} - -public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs - { - [JsonProperty("controlMode", NullValueHandling = NullValueHandling.Ignore)] - public eCameraControlMode ControlMode { get; protected set; } - - #region IRoutingOutputs Members - - [JsonIgnore] - public RoutingPortCollection OutputPorts { get; protected set; } - - #endregion - - [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] - public bool CanPan + /// + /// Enumeration of eCameraCapabilities values + /// + public enum eCameraCapabilities { - get - { - return (Capabilities & eCameraCapabilities.Pan) == eCameraCapabilities.Pan; - } - } - [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] - public bool CanTilt - { - get - { - return (Capabilities & eCameraCapabilities.Tilt) == eCameraCapabilities.Tilt; - } - } - [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] - public bool CanZoom - { - get - { - return (Capabilities & eCameraCapabilities.Zoom) == eCameraCapabilities.Zoom; - } - } - [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] - public bool CanFocus - { - get - { - return (Capabilities & eCameraCapabilities.Focus) == eCameraCapabilities.Focus; - } + /// + /// No camera capabilities + /// + None = 0, + /// + /// Camera supports pan movement + /// + Pan = 1, + /// + /// Camera supports tilt movement + /// + Tilt = 2, + /// + /// Camera supports zoom functionality + /// + Zoom = 4, + /// + /// Camera supports focus adjustment + /// + Focus = 8 } - // A bitmasked value to indicate the movement capabilites of this camera - protected eCameraCapabilities Capabilities { get; set; } - - protected CameraBase(DeviceConfig config) : base(config) - { - OutputPorts = new RoutingPortCollection(); - - ControlMode = eCameraControlMode.Manual; - - } - - protected CameraBase(string key, string name) : - this (new DeviceConfig{Name = name, Key = key}) + /// + /// Abstract base class for camera devices that provides common camera functionality and capabilities + /// + public abstract class CameraBase : ReconfigurableDevice, IRoutingOutputs { - - } + /// + /// Gets or sets the ControlMode + /// + [JsonProperty("controlMode", NullValueHandling = NullValueHandling.Ignore)] + public eCameraControlMode ControlMode { get; protected set; } - protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey, - EiscApiAdvanced bridge) - { - CameraControllerJoinMap joinMap = new CameraControllerJoinMap(joinStart); + #region IRoutingOutputs Members - if (bridge != null) + /// + /// Gets or sets the OutputPorts + /// + [JsonIgnore] + public RoutingPortCollection OutputPorts { get; protected set; } + + #endregion + + /// + /// Gets a value indicating whether this camera supports pan movement + /// + [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] + public bool CanPan { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); - } - - var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); - - if (customJoins != null) - { - joinMap.SetCustomJoinData(customJoins); - } - - Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - Debug.LogMessage(LogEventLevel.Information, "Linking to Bridge Type {0}", cameraDevice.GetType().Name.ToString()); - - var commMonitor = cameraDevice as ICommunicationMonitor; - commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig( - trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - - var ptzCamera = cameraDevice as IHasCameraPtzControl; - - if (ptzCamera != null) - { - trilist.SetBoolSigAction(joinMap.PanLeft.JoinNumber, (b) => + get { - if (b) - { - ptzCamera.PanLeft(); - } - else - { - ptzCamera.PanStop(); - } - }); - trilist.SetBoolSigAction(joinMap.PanRight.JoinNumber, (b) => - { - if (b) - { - ptzCamera.PanRight(); - } - else - { - ptzCamera.PanStop(); - } - }); - - trilist.SetBoolSigAction(joinMap.TiltUp.JoinNumber, (b) => - { - if (b) - { - ptzCamera.TiltUp(); - } - else - { - ptzCamera.TiltStop(); - } - }); - trilist.SetBoolSigAction(joinMap.TiltDown.JoinNumber, (b) => - { - if (b) - { - ptzCamera.TiltDown(); - } - else - { - ptzCamera.TiltStop(); - } - }); - - trilist.SetBoolSigAction(joinMap.ZoomIn.JoinNumber, (b) => - { - if (b) - { - ptzCamera.ZoomIn(); - } - else - { - ptzCamera.ZoomStop(); - } - }); - - trilist.SetBoolSigAction(joinMap.ZoomOut.JoinNumber, (b) => - { - if (b) - { - ptzCamera.ZoomOut(); - } - else - { - ptzCamera.ZoomStop(); - } - }); - } - - var powerCamera = cameraDevice as IHasPowerControl; - if (powerCamera != null) - { - trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => powerCamera.PowerOn()); - trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => powerCamera.PowerOff()); - - var powerFbCamera = powerCamera as IHasPowerControlWithFeedback; - if (powerFbCamera != null) - { - powerFbCamera.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); - powerFbCamera.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); + return (Capabilities & eCameraCapabilities.Pan) == eCameraCapabilities.Pan; } } - if (cameraDevice is ICommunicationMonitor) + /// + /// Gets a value indicating whether this camera supports tilt movement + /// + [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] + public bool CanTilt { - var monitoredCamera = cameraDevice as ICommunicationMonitor; - monitoredCamera.CommunicationMonitor.IsOnlineFeedback.LinkInputSig( + get + { + return (Capabilities & eCameraCapabilities.Tilt) == eCameraCapabilities.Tilt; + } + } + + /// + /// Gets a value indicating whether this camera supports zoom functionality + /// + [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] + public bool CanZoom + { + get + { + return (Capabilities & eCameraCapabilities.Zoom) == eCameraCapabilities.Zoom; + } + } + + /// + /// Gets a value indicating whether this camera supports focus adjustment + /// + [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] + public bool CanFocus + { + get + { + return (Capabilities & eCameraCapabilities.Focus) == eCameraCapabilities.Focus; + } + } + + /// + /// Gets or sets a bitmasked value to indicate the movement capabilities of this camera + /// + protected eCameraCapabilities Capabilities { get; set; } + + /// + /// Initializes a new instance of the CameraBase class with the specified device configuration + /// + /// The device configuration + protected CameraBase(DeviceConfig config) : base(config) + { + OutputPorts = new RoutingPortCollection(); + + ControlMode = eCameraControlMode.Manual; + + } + + /// + /// Initializes a new instance of the CameraBase class with the specified key and name + /// + /// The unique key for this camera device + /// The friendly name for this camera device + protected CameraBase(string key, string name) : + this(new DeviceConfig { Name = name, Key = key }) + { + + } + + /// + /// Links the camera device to the API bridge for control and feedback + /// + /// The camera device to link + /// The trilist for communication + /// The starting join number for the camera controls + /// The join map key for custom join mappings + /// The EiscApiAdvanced bridge for advanced join mapping + protected void LinkCameraToApi(CameraBase cameraDevice, BasicTriList trilist, uint joinStart, string joinMapKey, + EiscApiAdvanced bridge) + { + CameraControllerJoinMap joinMap = new CameraControllerJoinMap(joinStart); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); + + if (customJoins != null) + { + joinMap.SetCustomJoinData(customJoins); + } + + Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + Debug.LogMessage(LogEventLevel.Information, "Linking to Bridge Type {0}", cameraDevice.GetType().Name.ToString()); + + var commMonitor = cameraDevice as ICommunicationMonitor; + commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig( trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - } - if (cameraDevice is IHasCameraPresets) - { - // Set the preset lables when they change - var presetsCamera = cameraDevice as IHasCameraPresets; - presetsCamera.PresetsListHasChanged += new EventHandler((o, a) => + var ptzCamera = cameraDevice as IHasCameraPtzControl; + + if (ptzCamera != null) { - SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); - }); - - SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); - - for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++) - { - int tempNum = i; - - trilist.SetSigTrueAction((ushort) (joinMap.PresetRecallStart.JoinNumber + tempNum), () => + trilist.SetBoolSigAction(joinMap.PanLeft.JoinNumber, (b) => { - presetsCamera.PresetSelect(tempNum); + if (b) + { + ptzCamera.PanLeft(); + } + else + { + ptzCamera.PanStop(); + } }); - trilist.SetSigTrueAction((ushort) (joinMap.PresetSaveStart.JoinNumber + tempNum), () => + trilist.SetBoolSigAction(joinMap.PanRight.JoinNumber, (b) => { - var label = trilist.GetString((ushort) (joinMap.PresetLabelStart.JoinNumber + tempNum)); + if (b) + { + ptzCamera.PanRight(); + } + else + { + ptzCamera.PanStop(); + } + }); - presetsCamera.PresetStore(tempNum, label); + trilist.SetBoolSigAction(joinMap.TiltUp.JoinNumber, (b) => + { + if (b) + { + ptzCamera.TiltUp(); + } + else + { + ptzCamera.TiltStop(); + } + }); + trilist.SetBoolSigAction(joinMap.TiltDown.JoinNumber, (b) => + { + if (b) + { + ptzCamera.TiltDown(); + } + else + { + ptzCamera.TiltStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.ZoomIn.JoinNumber, (b) => + { + if (b) + { + ptzCamera.ZoomIn(); + } + else + { + ptzCamera.ZoomStop(); + } + }); + + trilist.SetBoolSigAction(joinMap.ZoomOut.JoinNumber, (b) => + { + if (b) + { + ptzCamera.ZoomOut(); + } + else + { + ptzCamera.ZoomStop(); + } }); } - trilist.OnlineStatusChange += (sender, args) => + + var powerCamera = cameraDevice as IHasPowerControl; + if (powerCamera != null) { - if (!args.DeviceOnLine) - { return; } + trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => powerCamera.PowerOn()); + trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => powerCamera.PowerOff()); + + var powerFbCamera = powerCamera as IHasPowerControlWithFeedback; + if (powerFbCamera != null) + { + powerFbCamera.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); + powerFbCamera.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); + } + } + + if (cameraDevice is ICommunicationMonitor) + { + var monitoredCamera = cameraDevice as ICommunicationMonitor; + monitoredCamera.CommunicationMonitor.IsOnlineFeedback.LinkInputSig( + trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + } + + if (cameraDevice is IHasCameraPresets) + { + // Set the preset lables when they change + var presetsCamera = cameraDevice as IHasCameraPresets; + presetsCamera.PresetsListHasChanged += new EventHandler((o, a) => + { + SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); + }); SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); - }; + for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++) + { + int tempNum = i; + + trilist.SetSigTrueAction((ushort)(joinMap.PresetRecallStart.JoinNumber + tempNum), () => + { + presetsCamera.PresetSelect(tempNum); + }); + trilist.SetSigTrueAction((ushort)(joinMap.PresetSaveStart.JoinNumber + tempNum), () => + { + var label = trilist.GetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum)); + + presetsCamera.PresetStore(tempNum, label); + }); + } + trilist.OnlineStatusChange += (sender, args) => + { + if (!args.DeviceOnLine) + { return; } + + SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist); + }; + + } } - } - private void SendCameraPresetNamesToApi(IHasCameraPresets presetsCamera, CameraControllerJoinMap joinMap, BasicTriList trilist) - { - for (int i = 1; i <= joinMap.NumberOfPresets.JoinNumber; i++) + private void SendCameraPresetNamesToApi(IHasCameraPresets presetsCamera, CameraControllerJoinMap joinMap, BasicTriList trilist) { - int tempNum = i - 1; + for (int i = 1; i <= joinMap.NumberOfPresets.JoinNumber; i++) + { + int tempNum = i - 1; - string label = ""; + string label = ""; - var preset = presetsCamera.Presets.FirstOrDefault(p => p.ID.Equals(i)); + var preset = presetsCamera.Presets.FirstOrDefault(p => p.ID.Equals(i)); - if (preset != null) - label = preset.Description; + if (preset != null) + label = preset.Description; - trilist.SetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum), label); + trilist.SetString((ushort)(joinMap.PresetLabelStart.JoinNumber + tempNum), label); + } } } - } -public class CameraPreset : PresetBase -{ - public CameraPreset(int id, string description, bool isDefined, bool isDefinable) - : base(id, description, isDefined, isDefinable) + /// + /// Represents a CameraPreset + /// + public class CameraPreset : PresetBase { + /// + /// Initializes a new instance of the CameraPreset class + /// + /// The preset ID + /// The preset description + /// Whether the preset is defined + /// Whether the preset can be defined + public CameraPreset(int id, string description, bool isDefined, bool isDefinable) + : base(id, description, isDefined, isDefinable) + { + } } -} /// @@ -288,12 +361,22 @@ public class CameraPreset : PresetBase /// public ControlPropertiesConfig Control { get; set; } - [JsonProperty("supportsAutoMode")] - public bool SupportsAutoMode { get; set; } + /// + /// Gets or sets the SupportsAutoMode + /// + [JsonProperty("supportsAutoMode")] + public bool SupportsAutoMode { get; set; } - [JsonProperty("supportsOffMode")] - public bool SupportsOffMode { get; set; } + /// + /// Gets or sets the SupportsOffMode + /// + [JsonProperty("supportsOffMode")] + public bool SupportsOffMode { get; set; } - [JsonProperty("presets")] - public List Presets { get; set; } - } + /// + /// Gets or sets the Presets + /// + [JsonProperty("presets")] + public List Presets { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/CameraVisca.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraVisca.cs new file mode 100644 index 00000000..f383dd0a --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/CameraVisca.cs @@ -0,0 +1,846 @@ + + +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharpPro.DeviceSupport; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Bridges; +using PepperDash.Essentials.Core.Config; +using Serilog.Events; + +namespace PepperDash.Essentials.Devices.Common.Cameras +{ + /// + /// Represents a CameraVisca + /// + [Obsolete("CameraVisca is no longer supported and will be removed in a future release. Use the CameraVisca plugin instead.")] + + public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IHasPowerControlWithFeedback, IBridgeAdvanced, IHasCameraFocusControl, IHasAutoFocusMode + { + private readonly CameraViscaPropertiesConfig PropertiesConfig; + + /// + /// Gets or sets the Communication + /// + public IBasicCommunication Communication { get; private set; } + + /// + /// Gets or sets the CommunicationMonitor + /// + public StatusMonitorBase CommunicationMonitor { get; private set; } + + /// + /// Used to store the actions to parse inquiry responses as the inquiries are sent + /// + private readonly CrestronQueue> InquiryResponseQueue; + + /// + /// Camera ID (Default 1) + /// + public byte ID = 0x01; + + /// + /// Response ID used for VISCA communication + /// + public byte ResponseID; + + /// + /// Slow speed value for pan movement + /// + public byte PanSpeedSlow = 0x10; + + /// + /// Slow speed value for tilt movement + /// + public byte TiltSpeedSlow = 0x10; + + /// + /// Fast speed value for pan movement + /// + public byte PanSpeedFast = 0x13; + + /// + /// Fast speed value for tilt movement + /// + public byte TiltSpeedFast = 0x13; + + // private bool IsMoving; + private bool IsZooming; + + bool _powerIsOn; + + /// + /// Gets or sets a value indicating whether the camera power is on + /// + public bool PowerIsOn + { + get + { + return _powerIsOn; + } + private set + { + if (value != _powerIsOn) + { + _powerIsOn = value; + PowerIsOnFeedback.FireUpdate(); + CameraIsOffFeedback.FireUpdate(); + } + } + } + + const byte ZoomInCmd = 0x02; + const byte ZoomOutCmd = 0x03; + const byte ZoomStopCmd = 0x00; + + /// + /// Used to determine when to move the camera at a faster speed if a direction is held + /// + CTimer SpeedTimer; + // TODO: Implment speed timer for PTZ controls + + long FastSpeedHoldTimeMs = 2000; + + byte[] IncomingBuffer = new byte[] { }; + + /// + /// Feedback indicating whether the camera power is on + /// + public BoolFeedback PowerIsOnFeedback { get; private set; } + + /// + /// Initializes a new instance of the CameraVisca class + /// + /// The unique key for this camera device + /// The friendly name for this camera device + /// The communication interface for VISCA protocol + /// The camera properties configuration + public CameraVisca(string key, string name, IBasicCommunication comm, CameraViscaPropertiesConfig props) : + base(key, name) + { + InquiryResponseQueue = new CrestronQueue>(15); + + Presets = props.Presets; + + PropertiesConfig = props; + + ID = (byte)(props.Id + 0x80); + ResponseID = (byte)((props.Id * 0x10) + 0x80); + + SetupCameraSpeeds(); + + OutputPorts.Add(new RoutingOutputPort("videoOut", eRoutingSignalType.Video, eRoutingPortConnectionType.None, null, this, true)); + + // Default to all capabilties + Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus; + + Communication = comm; + if (comm is ISocketStatus socket) + { + // This instance uses IP control + socket.ConnectionChange += new EventHandler(Socket_ConnectionChange); + } + else + { + // This instance uses RS-232 control + } + + Communication.BytesReceived += new EventHandler(Communication_BytesReceived); + PowerIsOnFeedback = new BoolFeedback("powerIsOn", () => { return PowerIsOn; }); + CameraIsOffFeedback = new BoolFeedback("cameraIsOff", () => { return !PowerIsOn; }); + + if (props.CommunicationMonitorProperties != null) + { + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties); + } + else + { + CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 20000, 120000, 300000, "\x81\x09\x04\x00\xFF"); + } + DeviceManager.AddDevice(CommunicationMonitor); + } + + + /// + /// Sets up camera speed values based on config + /// + void SetupCameraSpeeds() + { + if (PropertiesConfig.FastSpeedHoldTimeMs > 0) + { + FastSpeedHoldTimeMs = PropertiesConfig.FastSpeedHoldTimeMs; + } + + if (PropertiesConfig.PanSpeedSlow > 0) + { + PanSpeedSlow = (byte)PropertiesConfig.PanSpeedSlow; + } + if (PropertiesConfig.PanSpeedFast > 0) + { + PanSpeedFast = (byte)PropertiesConfig.PanSpeedFast; + } + + if (PropertiesConfig.TiltSpeedSlow > 0) + { + TiltSpeedSlow = (byte)PropertiesConfig.TiltSpeedSlow; + } + if (PropertiesConfig.TiltSpeedFast > 0) + { + TiltSpeedFast = (byte)PropertiesConfig.TiltSpeedFast; + } + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + Communication.Connect(); + + + CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); }; + CommunicationMonitor.Start(); + + + CrestronConsole.AddNewConsoleCommand(s => Communication.Connect(), "con" + Key, "", ConsoleAccessLevelEnum.AccessOperator); + return true; + } + + /// + /// LinkToApi method + /// + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge); + } + + void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Socket Status Change: {0}", e.Client.ClientStatus.ToString()); + + if (e.Client.IsConnected) + { + + } + else + { + + } + } + + + void SendBytes(byte[] b) + { + + if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1 + Debug.LogMessage(LogEventLevel.Verbose, this, "Sending:{0}", ComTextHelper.GetEscapedText(b)); + + Communication.SendBytes(b); + } + + void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) + { + var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length]; + + try + { + // This is probably not thread-safe buffering + // Append the incoming bytes with whatever is in the buffer + IncomingBuffer.CopyTo(newBytes, 0); + e.Bytes.CopyTo(newBytes, IncomingBuffer.Length); + if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1 + Debug.LogMessage(LogEventLevel.Verbose, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes)); + + byte[] message = new byte[] { }; + + // Search for the delimiter 0xFF character + for (int i = 0; i < newBytes.Length; i++) + { + if (newBytes[i] == 0xFF) + { + // i will be the index of the delmiter character + message = newBytes.Take(i).ToArray(); + // Skip over what we just took and save the rest for next time + newBytes = newBytes.Skip(i).ToArray(); + } + } + + if (message.Length > 0) + { + // Check for matching ID + if (message[0] != ResponseID) + { + return; + } + + switch (message[1]) + { + case 0x40: + { + // ACK received + Debug.LogMessage(LogEventLevel.Verbose, this, "ACK Received"); + break; + } + case 0x50: + { + + if (message[2] == 0xFF) + { + // Completion received + Debug.LogMessage(LogEventLevel.Verbose, this, "Completion Received"); + } + else + { + // Inquiry response received. Dequeue the next response handler and invoke it + if (InquiryResponseQueue.Count > 0) + { + var inquiryAction = InquiryResponseQueue.Dequeue(); + + inquiryAction.Invoke(message.Skip(2).ToArray()); + } + else + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Response Queue is empty. Nothing to dequeue."); + } + } + + break; + } + case 0x60: + { + // Error message + + switch (message[2]) + { + case 0x01: + { + // Message Length Error + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Message Length Error"); + break; + } + case 0x02: + { + // Syntax Error + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Syntax Error"); + break; + } + case 0x03: + { + // Command Buffer Full + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command Buffer Full"); + break; + } + case 0x04: + { + // Command Cancelled + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command Cancelled"); + break; + } + case 0x05: + { + // No Socket + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: No Socket"); + break; + } + case 0x41: + { + // Command not executable + Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command not executable"); + break; + } + } + break; + } + } + + if (message == new byte[] { ResponseID, 0x50, 0x02, 0xFF }) + { + PowerIsOn = true; + } + else if (message == new byte[] { ResponseID, 0x50, 0x03, 0xFF }) + { + PowerIsOn = false; + } + + } + + } + catch (Exception err) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Error parsing feedback: {0}", err); + } + finally + { + // Save whatever partial message is here + IncomingBuffer = newBytes; + } + } + + /// + /// Sends a pan/tilt command. If the command is not for fastSpeed then it starts a timer to initiate fast speed. + /// + /// The VISCA command to send + /// Whether fast speed is enabled for this command + private void SendPanTiltCommand(byte[] cmd, bool fastSpeedEnabled) + { + SendBytes(GetPanTiltCommand(cmd, fastSpeedEnabled)); + + if (!fastSpeedEnabled) + { + if (SpeedTimer != null) + { + StopSpeedTimer(); + } + + // Start the timer to send fast speed if still moving after FastSpeedHoldTime elapses + SpeedTimer = new CTimer((o) => SendPanTiltCommand(GetPanTiltCommand(cmd, true), true), FastSpeedHoldTimeMs); + } + + } + + private void StopSpeedTimer() + { + if (SpeedTimer != null) + { + SpeedTimer.Stop(); + SpeedTimer.Dispose(); + SpeedTimer = null; + } + } + + /// + /// Generates the pan/tilt command with either slow or fast speed + /// + /// + /// + /// + private byte[] GetPanTiltCommand(byte[] cmd, bool fastSpeed) + { + byte panSpeed; + byte tiltSpeed; + + if (!fastSpeed) + { + panSpeed = PanSpeedSlow; + tiltSpeed = TiltSpeedSlow; + } + else + { + panSpeed = PanSpeedFast; + tiltSpeed = TiltSpeedFast; + } + + var temp = new byte[] { ID, 0x01, 0x06, 0x01, panSpeed, tiltSpeed }; + int length = temp.Length + cmd.Length + 1; + + byte[] sum = new byte[length]; + temp.CopyTo(sum, 0); + cmd.CopyTo(sum, temp.Length); + sum[length - 1] = 0xFF; + + return sum; + } + + + void SendPowerQuery() + { + SendBytes(new byte[] { ID, 0x09, 0x04, 0x00, 0xFF }); + InquiryResponseQueue.Enqueue(HandlePowerResponse); + } + + /// + /// PowerOn method + /// + public void PowerOn() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x02, 0xFF }); + SendPowerQuery(); + } + + void HandlePowerResponse(byte[] response) + { + switch (response[0]) + { + case 0x02: + { + PowerIsOn = true; + break; + } + case 0x03: + { + PowerIsOn = false; + break; + } + } + } + + /// + /// PowerOff method + /// + public void PowerOff() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x03, 0xFF }); + SendPowerQuery(); + } + + /// + /// PowerToggle method + /// + public void PowerToggle() + { + if (PowerIsOnFeedback.BoolValue) + PowerOff(); + else + PowerOn(); + } + + /// + /// PanLeft method + /// + public void PanLeft() + { + SendPanTiltCommand(new byte[] { 0x01, 0x03 }, false); + // IsMoving = true; + } + /// + /// PanRight method + /// + public void PanRight() + { + SendPanTiltCommand(new byte[] { 0x02, 0x03 }, false); + // IsMoving = true; + } + /// + /// PanStop method + /// + public void PanStop() + { + Stop(); + } + /// + /// TiltDown method + /// + public void TiltDown() + { + SendPanTiltCommand(new byte[] { 0x03, 0x02 }, false); + // IsMoving = true; + } + /// + /// TiltUp method + /// + public void TiltUp() + { + SendPanTiltCommand(new byte[] { 0x03, 0x01 }, false); + // IsMoving = true; + } + /// + /// TiltStop method + /// + public void TiltStop() + { + Stop(); + } + + private void SendZoomCommand(byte cmd) + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x07, cmd, 0xFF }); + } + + + /// + /// ZoomIn method + /// + public void ZoomIn() + { + SendZoomCommand(ZoomInCmd); + IsZooming = true; + } + /// + /// ZoomOut method + /// + public void ZoomOut() + { + SendZoomCommand(ZoomOutCmd); + IsZooming = true; + } + /// + /// ZoomStop method + /// + public void ZoomStop() + { + Stop(); + } + + /// + /// Stop method + /// + public void Stop() + { + if (IsZooming) + { + SendZoomCommand(ZoomStopCmd); + IsZooming = false; + } + else + { + StopSpeedTimer(); + SendPanTiltCommand(new byte[] { 0x03, 0x03 }, false); + // IsMoving = false; + } + } + /// + /// PositionHome method + /// + public void PositionHome() + { + SendBytes(new byte[] { ID, 0x01, 0x06, 0x02, PanSpeedFast, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF }); + SendBytes(new byte[] { ID, 0x01, 0x04, 0x47, 0x00, 0x00, 0x00, 0x00, 0xFF }); + } + /// + /// RecallPreset method + /// + public void RecallPreset(int presetNumber) + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF }); + } + /// + /// SavePreset method + /// + public void SavePreset(int presetNumber) + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF }); + } + + #region IHasCameraPresets Members + + /// + /// Event that is raised when the presets list has changed + /// + public event EventHandler PresetsListHasChanged; + + /// + /// Raises the PresetsListHasChanged event + /// + protected void OnPresetsListHasChanged() + { + var handler = PresetsListHasChanged; + if (handler == null) + return; + + handler.Invoke(this, EventArgs.Empty); + } + + /// + /// Gets or sets the Presets + /// + public List Presets { get; private set; } + + /// + /// PresetSelect method + /// + public void PresetSelect(int preset) + { + RecallPreset(preset); + } + + /// + /// PresetStore method + /// + public void PresetStore(int preset, string description) + { + SavePreset(preset); + } + + + #endregion + + #region IHasCameraFocusControl Members + + /// + /// FocusNear method + /// + public void FocusNear() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x03, 0xFF }); + } + + /// + /// FocusFar method + /// + public void FocusFar() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x02, 0xFF }); + } + + /// + /// FocusStop method + /// + public void FocusStop() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x00, 0xFF }); + } + + /// + /// TriggerAutoFocus method + /// + public void TriggerAutoFocus() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x18, 0x01, 0xFF }); + SendAutoFocusQuery(); + } + + #endregion + + #region IHasAutoFocus Members + + /// + /// SetFocusModeAuto method + /// + public void SetFocusModeAuto() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x02, 0xFF }); + SendAutoFocusQuery(); + } + + /// + /// SetFocusModeManual method + /// + public void SetFocusModeManual() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x03, 0xFF }); + SendAutoFocusQuery(); + } + + /// + /// ToggleFocusMode method + /// + public void ToggleFocusMode() + { + SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x10, 0xFF }); + SendAutoFocusQuery(); + } + + #endregion + + void SendAutoFocusQuery() + { + SendBytes(new byte[] { ID, 0x09, 0x04, 0x38, 0xFF }); + InquiryResponseQueue.Enqueue(HandleAutoFocusResponse); + } + + void HandleAutoFocusResponse(byte[] response) + { + switch (response[0]) + { + case 0x02: + { + // Auto Mode + PowerIsOn = true; + break; + } + case 0x03: + { + // Manual Mode + PowerIsOn = false; + break; + } + } + } + + #region IHasCameraOff Members + + /// + /// Gets or sets the CameraIsOffFeedback + /// + public BoolFeedback CameraIsOffFeedback { get; private set; } + + + /// + /// CameraOff method + /// + public void CameraOff() + { + PowerOff(); + } + + #endregion + } + + /// + /// Represents a CameraViscaFactory + /// + public class CameraViscaFactory : EssentialsDeviceFactory + { + /// + /// Initializes a new instance of the CameraViscaFactory class + /// + public CameraViscaFactory() + { + TypeNames = new List() { "cameravisca" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new CameraVisca Device"); + var comm = CommFactory.CreateCommForDevice(dc); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject( + dc.Properties.ToString()); + return new Cameras.CameraVisca(dc.Key, dc.Name, comm, props); + } + } + + + /// + /// Represents a CameraViscaPropertiesConfig + /// + public class CameraViscaPropertiesConfig : CameraPropertiesConfig + { + /// + /// Control ID of the camera (1-7) + /// Gets or sets the Id + /// + [JsonProperty("id")] + public uint Id { get; set; } + + /// + /// Slow Pan speed (0-18) + /// + [JsonProperty("panSpeedSlow")] + public uint PanSpeedSlow { get; set; } + + /// + /// Fast Pan speed (0-18) + /// + [JsonProperty("panSpeedFast")] + public uint PanSpeedFast { get; set; } + + /// + /// Slow tilt speed (0-18) + /// + [JsonProperty("tiltSpeedSlow")] + public uint TiltSpeedSlow { get; set; } + + /// + /// Fast tilt speed (0-18) + /// + [JsonProperty("tiltSpeedFast")] + public uint TiltSpeedFast { get; set; } + + /// + /// Time a button must be held before fast speed is engaged (Milliseconds) + /// + [JsonProperty("fastSpeedHoldTimeMs")] + public uint FastSpeedHoldTimeMs { get; set; } + + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/IHasCameraPresets.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/IHasCameraPresets.cs index 2fcdb9aa..0234a405 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/IHasCameraPresets.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/IHasCameraPresets.cs @@ -1,34 +1,34 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Devices.Common.Cameras; - - -/// -/// Describes a camera with preset functionality -/// -public interface IHasCameraPresets +namespace PepperDash.Essentials.Devices.Common.Cameras { /// - /// Event that is raised when the presets list has changed + /// Describes a camera with preset functionality /// - event EventHandler PresetsListHasChanged; + public interface IHasCameraPresets + { + /// + /// Event that is raised when the presets list has changed + /// + event EventHandler PresetsListHasChanged; - /// - /// Gets the list of camera presets - /// - List Presets { get; } + /// + /// Gets the list of camera presets + /// + List Presets { get; } - /// - /// Selects the specified preset - /// - /// The preset number to select - void PresetSelect(int preset); + /// + /// Selects the specified preset + /// + /// The preset number to select + void PresetSelect(int preset); - /// - /// Stores a preset at the specified location with the given description - /// - /// The preset number to store - /// The description for the preset - void PresetStore(int preset, string description); -} + /// + /// Stores a preset at the specified location with the given description + /// + /// The preset number to store + /// The description for the preset + void PresetStore(int preset, string description); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/CameraSelectedEventArgs.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/CameraSelectedEventArgs.cs index d669a567..b96a9b3f 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/CameraSelectedEventArgs.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/CameraSelectedEventArgs.cs @@ -1,7 +1,32 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace PepperDash.Essentials.Devices.Common.Cameras { + /// + /// Event arguments for the CameraSelected event + /// + [Obsolete("Use CameraSelectedEventArgs instead. This class will be removed in a future version")] + public class CameraSelectedEventArgs : EventArgs + { + /// + /// Gets or sets the SelectedCamera + /// + public CameraBase SelectedCamera { get; private set; } + + /// + /// Constructor for CameraSelectedEventArgs + /// + /// + public CameraSelectedEventArgs(CameraBase camera) + { + SelectedCamera = camera; + } + } + /// /// Event arguments for the CameraSelected event /// diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCameras.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCameras.cs new file mode 100644 index 00000000..5a5962f8 --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCameras.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.Devices.Common.Cameras +{ + /// + /// Interface for devices that have cameras + /// + [Obsolete("Use IHasCamerasWithControls instead. This interface will be removed in a future version")] + public interface IHasCameras : IKeyName + { + /// + /// Event that is raised when a camera is selected + /// + event EventHandler CameraSelected; + + /// + /// List of cameras on the device. This should be a list of CameraBase objects + /// + List Cameras { get; } + + /// + /// The currently selected camera. This should be a CameraBase object + /// + CameraBase SelectedCamera { get; } + + /// + /// Feedback that indicates the currently selected camera + /// + StringFeedback SelectedCameraFeedback { get; } + + /// + /// Selects a camera from the list of available cameras based on the provided key. + /// + /// The unique identifier or name of the camera to select. + void SelectCamera(string key); + } +} diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCamerasWithControls.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCamerasWithControls.cs index e4b76572..f7aff6d2 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCamerasWithControls.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCamerasWithControls.cs @@ -8,7 +8,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// /// Interface for devices that have cameras with controls /// - public interface IHasCamerasWithControls : IKeyName + public interface IHasCamerasWithControls : IKeyName, IKeyed { /// /// List of cameras on the device. This should be a list of IHasCameraControls objects diff --git a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCodecCameras.cs b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCodecCameras.cs index 32c312f7..01cdf24e 100644 --- a/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCodecCameras.cs +++ b/src/PepperDash.Essentials.Devices.Common/Cameras/Interfaces/IHasCodecCameras.cs @@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras /// /// Defines the contract for IHasCodecCameras /// - public interface IHasCodecCameras : IHasCamerasWithControls, IHasFarEndCameraControl + public interface IHasCodecCameras : IHasCameras, IHasFarEndCameraControl { } diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/IPresenterTrack.cs b/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/IPresenterTrack.cs index b98cb4e7..134dc7b5 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/IPresenterTrack.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/IPresenterTrack.cs @@ -1,89 +1,90 @@ using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Codec.Cisco; - -/// -/// Describes the available tracking modes for a Cisco codec's Presenter Track feature. -/// -public enum ePresenterTrackMode +namespace PepperDash.Essentials.Devices.Common.Codec.Cisco { /// - /// Presenter Track is turned off. + /// Describes the available tracking modes for a Cisco codec's Presenter Track feature. /// - Off, + public enum ePresenterTrackMode + { + /// + /// Presenter Track is turned off. + /// + Off, + /// + /// Presenter Track follows the speaker's movements. + /// + Follow, + /// + /// Presenter Track is set to background mode, where it tracks the speaker but does not actively follow. + /// + Background, + /// + /// Presenter Track is set to persistent mode, where it maintains a fixed position or focus on the speaker. + /// + Persistent + } + + /// - /// Presenter Track follows the speaker's movements. + /// Describes the Presenter Track controls for a Cisco codec. /// - Follow, - /// - /// Presenter Track is set to background mode, where it tracks the speaker but does not actively follow. - /// - Background, - /// - /// Presenter Track is set to persistent mode, where it maintains a fixed position or focus on the speaker. - /// - Persistent -} - - -/// -/// Describes the Presenter Track controls for a Cisco codec. -/// -public interface IPresenterTrack : IKeyed -{ - /// - /// - /// - bool PresenterTrackAvailability { get; } - - /// - /// Feedback indicating whether Presenter Track is available. - /// - BoolFeedback PresenterTrackAvailableFeedback { get; } - - /// - /// Feedback indicating the current status of Presenter Track is off - /// - BoolFeedback PresenterTrackStatusOffFeedback { get; } - - /// - /// Feedback indicating the current status of Presenter Track is follow - /// - BoolFeedback PresenterTrackStatusFollowFeedback { get; } - - /// - /// Feedback indicating the current status of Presenter Track is background - /// - BoolFeedback PresenterTrackStatusBackgroundFeedback { get; } - - /// - /// Feedback indicating the current status of Presenter Track is persistent - /// - BoolFeedback PresenterTrackStatusPersistentFeedback { get; } - - /// - /// Indicates the current status of Presenter Track. - /// - bool PresenterTrackStatus { get; } - - /// - /// Turns off Presenter Track. - /// - void PresenterTrackOff(); - - /// - /// Turns on Presenter Track in follow mode. - /// - void PresenterTrackFollow(); - - /// - /// Turns on Presenter Track in background mode. - /// - void PresenterTrackBackground(); - - /// - /// Turns on Presenter Track in persistent mode. - /// - void PresenterTrackPersistent(); + public interface IPresenterTrack : IKeyed + { + /// + /// + /// + bool PresenterTrackAvailability { get; } + + /// + /// Feedback indicating whether Presenter Track is available. + /// + BoolFeedback PresenterTrackAvailableFeedback { get; } + + /// + /// Feedback indicating the current status of Presenter Track is off + /// + BoolFeedback PresenterTrackStatusOffFeedback { get; } + + /// + /// Feedback indicating the current status of Presenter Track is follow + /// + BoolFeedback PresenterTrackStatusFollowFeedback { get; } + + /// + /// Feedback indicating the current status of Presenter Track is background + /// + BoolFeedback PresenterTrackStatusBackgroundFeedback { get; } + + /// + /// Feedback indicating the current status of Presenter Track is persistent + /// + BoolFeedback PresenterTrackStatusPersistentFeedback { get; } + + /// + /// Indicates the current status of Presenter Track. + /// + bool PresenterTrackStatus { get; } + + /// + /// Turns off Presenter Track. + /// + void PresenterTrackOff(); + + /// + /// Turns on Presenter Track in follow mode. + /// + void PresenterTrackFollow(); + + /// + /// Turns on Presenter Track in background mode. + /// + void PresenterTrackBackground(); + + /// + /// Turns on Presenter Track in persistent mode. + /// + void PresenterTrackPersistent(); + } } diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/ISpeakerTrack.cs b/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/ISpeakerTrack.cs index 5e4726b2..522e6ad2 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/ISpeakerTrack.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/Cisco/ISpeakerTrack.cs @@ -1,34 +1,35 @@ using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Codec.Cisco; - -/// -/// Describes the available tracking modes for a Cisco codec -/// -public interface ISpeakerTrack : IKeyed +namespace PepperDash.Essentials.Devices.Common.Codec.Cisco { /// - /// Indicates whether Speaker Track is available on the codec. + /// Describes the available tracking modes for a Cisco codec /// - bool SpeakerTrackAvailability { get; } + public interface ISpeakerTrack : IKeyed + { + /// + /// Indicates whether Speaker Track is available on the codec. + /// + bool SpeakerTrackAvailability { get; } - /// - /// - /// - BoolFeedback SpeakerTrackAvailableFeedback { get; } + /// + /// + /// + BoolFeedback SpeakerTrackAvailableFeedback { get; } - /// - /// Feedback indicating the current status of Speaker Track is off - /// - bool SpeakerTrackStatus { get; } + /// + /// Feedback indicating the current status of Speaker Track is off + /// + bool SpeakerTrackStatus { get; } - /// - /// Turns Speaker Track off - /// - void SpeakerTrackOff(); - /// - /// Turns Speaker Track on - /// - void SpeakerTrackOn(); + /// + /// Turns Speaker Track off + /// + void SpeakerTrackOff(); + /// + /// Turns Speaker Track on + /// + void SpeakerTrackOn(); + } } diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/CodecActiveCallItem.cs b/src/PepperDash.Essentials.Devices.Common/Codec/CodecActiveCallItem.cs index b10e816b..23a39f29 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/CodecActiveCallItem.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/CodecActiveCallItem.cs @@ -1,100 +1,105 @@ -using System; + + +using System; + using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PepperDash.Essentials.Devices.Common.Codec; +namespace PepperDash.Essentials.Devices.Common.Codec -/// -/// Represents an active call item for a codec, including details such as name, number, type, status, direction, and duration. -/// -public class CodecActiveCallItem { /// - /// The name associated with the call, if available. + /// Represents a CodecActiveCallItem /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } - - /// - /// The phone number associated with the call, if available. - /// - [JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)] - public string Number { get; set; } - - /// - /// The type of the call, such as audio or video. This is an optional property and may be null if the type is unknown or not applicable. - /// - [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(StringEnumConverter))] - public eCodecCallType Type { get; set; } - - /// - /// The current status of the call, such as active, on hold, or disconnected. This is an optional property and may be null if the status is unknown or not applicable. - /// - [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(StringEnumConverter))] - public eCodecCallStatus Status { get; set; } - - /// - /// The direction of the call, such as incoming or outgoing. This is an optional property and may be null if the direction is unknown or not applicable. - /// - [JsonProperty("direction", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(StringEnumConverter))] - public eCodecCallDirection Direction { get; set; } - - /// - /// A unique identifier for the call, if available. This can be used to track the call across different events and status changes. This is an optional property and may be null if an identifier is not available or applicable. - /// - [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] - public string Id { get; set; } - - /// - /// Indicates whether the call is currently on hold. This is an optional property and may be null if the hold status is unknown or not applicable. - /// - [JsonProperty("isOnHold", NullValueHandling = NullValueHandling.Ignore)] - public bool IsOnHold { get; set; } - - /// - /// The duration of the call, if available. This can be used to track how long the call has been active. This is an optional property and may be null if the duration is not available or applicable. - /// - [JsonProperty("duration", NullValueHandling = NullValueHandling.Ignore)] - public TimeSpan Duration { get; set; } - - //public object CallMetaData { get; set; } - - /// - /// Returns true when this call is any status other than - /// Unknown, Disconnected, Disconnecting - /// - [JsonProperty("isActiveCall", NullValueHandling = NullValueHandling.Ignore)] - public bool IsActiveCall + public class CodecActiveCallItem { - get + /// + /// Gets or sets the Name + /// + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + /// + /// Gets or sets the Number + /// + [JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)] + public string Number { get; set; } + + /// + /// Gets or sets the Type + /// + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public eCodecCallType Type { get; set; } + + /// + /// Gets or sets the Status + /// + [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public eCodecCallStatus Status { get; set; } + + /// + /// Gets or sets the Direction + /// + [JsonProperty("direction", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] + public eCodecCallDirection Direction { get; set; } + + /// + /// Gets or sets the Id + /// + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] + public string Id { get; set; } + + /// + /// Gets or sets the IsOnHold + /// + [JsonProperty("isOnHold", NullValueHandling = NullValueHandling.Ignore)] + public bool IsOnHold { get; set; } + + /// + /// Gets or sets the Duration + /// + [JsonProperty("duration", NullValueHandling = NullValueHandling.Ignore)] + public TimeSpan Duration { get; set; } + + //public object CallMetaData { get; set; } + + /// + /// Returns true when this call is any status other than + /// Unknown, Disconnected, Disconnecting + /// + [JsonProperty("isActiveCall", NullValueHandling = NullValueHandling.Ignore)] + public bool IsActiveCall { - return !(Status == eCodecCallStatus.Disconnected - || Status == eCodecCallStatus.Disconnecting + get + { + return !(Status == eCodecCallStatus.Disconnected + || Status == eCodecCallStatus.Disconnecting || Status == eCodecCallStatus.Idle - || Status == eCodecCallStatus.Unknown); + || Status == eCodecCallStatus.Unknown); + } } } -} - -/// -/// Event args for when a call status changes, includes the call item with updated status and details. -/// -public class CodecCallStatusItemChangeEventArgs : EventArgs -{ - /// - /// The call item that has changed status, including its updated status and details. This property is read-only and is set through the constructor when the event args are created. - /// - public CodecActiveCallItem CallItem { get; private set; } /// - /// Constructor for CodecCallStatusItemChangeEventArgs + /// Represents a CodecCallStatusItemChangeEventArgs /// - /// The call item that has changed status. - public CodecCallStatusItemChangeEventArgs(CodecActiveCallItem item) + public class CodecCallStatusItemChangeEventArgs : EventArgs { - CallItem = item; + /// + /// Gets or sets the CallItem + /// + public CodecActiveCallItem CallItem { get; private set; } + + /// + /// Initializes a new instance of the CodecCallStatusItemChangeEventArgs class + /// + /// The call item that changed + public CodecCallStatusItemChangeEventArgs(CodecActiveCallItem item) + { + CallItem = item; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/CodecScheduleAwareness.cs b/src/PepperDash.Essentials.Devices.Common/Codec/CodecScheduleAwareness.cs index 777474db..4eea3d2a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/CodecScheduleAwareness.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/CodecScheduleAwareness.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -using System.Timers; +using Crestron.SimplSharp; using PepperDash.Core; using Serilog.Events; @@ -56,14 +56,16 @@ namespace PepperDash.Essentials.Devices.Common.Codec } } - private Timer _scheduleChecker; + private readonly CTimer _scheduleChecker; /// /// Initializes a new instance of the CodecScheduleAwareness class with default poll time /// public CodecScheduleAwareness() { - Init(1000); + Meetings = new List(); + + _scheduleChecker = new CTimer(CheckSchedule, null, 1000, 1000); } /// @@ -71,17 +73,10 @@ namespace PepperDash.Essentials.Devices.Common.Codec /// /// The poll time in milliseconds for checking schedule changes public CodecScheduleAwareness(long pollTime) - { - Init(pollTime); - } - - private void Init(long pollTime) { Meetings = new List(); - _scheduleChecker = new Timer(pollTime) { AutoReset = true }; - _scheduleChecker.Elapsed += (s, e) => CheckSchedule(null); - _scheduleChecker.Start(); + _scheduleChecker = new CTimer(CheckSchedule, null, pollTime, pollTime); } /// diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/IHasCallHold.cs b/src/PepperDash.Essentials.Devices.Common/Codec/IHasCallHold.cs index dddc7a6b..1445bd61 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/IHasCallHold.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/IHasCallHold.cs @@ -1,20 +1,20 @@ - -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Defines the contract for devices that have call hold functionality -/// -public interface IHasCallHold +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Put the specified call on hold + /// Defines the contract for IHasCallHold /// - /// The call to put on hold - void HoldCall(CodecActiveCallItem activeCall); + public interface IHasCallHold + { + /// + /// Put the specified call on hold + /// + /// + void HoldCall(CodecActiveCallItem activeCall); - /// - /// Resume the specified call - /// - /// The call to resume - void ResumeCall(CodecActiveCallItem activeCall); + /// + /// Resume the specified call + /// + /// + void ResumeCall(CodecActiveCallItem activeCall); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/IHasDoNotDisturb.cs b/src/PepperDash.Essentials.Devices.Common/Codec/IHasDoNotDisturb.cs index 4cb46ec7..c989e671 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/IHasDoNotDisturb.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/IHasDoNotDisturb.cs @@ -1,41 +1,42 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Describes a device that has Do Not Disturb mode capability -/// -public interface IHasDoNotDisturbMode +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Indictes whether Do Not Disturb mode is on (Activated) + /// Describes a device that has Do Not Disturb mode capability /// - BoolFeedback DoNotDisturbModeIsOnFeedback { get; } + public interface IHasDoNotDisturbMode + { + /// + /// Indictes whether Do Not Disturb mode is on (Activated) + /// + BoolFeedback DoNotDisturbModeIsOnFeedback { get; } + + /// + /// Activates Do Not Disturb mode + /// + void ActivateDoNotDisturbMode(); + + /// + /// Deactivates Do Not Disturb mode + /// + void DeactivateDoNotDisturbMode(); + + /// + /// Toggles Do Not Disturb mode + /// + void ToggleDoNotDisturbMode(); + } /// - /// Activates Do Not Disturb mode + /// Defines the contract for devices that support Do Not Disturb mode with timeout functionality /// - void ActivateDoNotDisturbMode(); - - /// - /// Deactivates Do Not Disturb mode - /// - void DeactivateDoNotDisturbMode(); - - /// - /// Toggles Do Not Disturb mode - /// - void ToggleDoNotDisturbMode(); -} - -/// -/// Defines the contract for devices that support Do Not Disturb mode with timeout functionality -/// -public interface IHasDoNotDisturbModeWithTimeout : IHasDoNotDisturbMode -{ - /// - /// Activates Do Not Disturb mode with a timeout - /// - /// - void ActivateDoNotDisturbMode(int timeout); -} + public interface IHasDoNotDisturbModeWithTimeout : IHasDoNotDisturbMode + { + /// + /// Activates Do Not Disturb mode with a timeout + /// + /// + void ActivateDoNotDisturbMode(int timeout); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/IHasExternalSourceSwitching.cs b/src/PepperDash.Essentials.Devices.Common/Codec/IHasExternalSourceSwitching.cs index 72742c2d..73884a2a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/IHasExternalSourceSwitching.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/IHasExternalSourceSwitching.cs @@ -1,54 +1,54 @@ using System; using PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Defines the contract for devices that support external source switching functionality -/// -public interface IHasExternalSourceSwitching +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Gets a value indicating whether the external source list is enabled + /// Defines the contract for IHasExternalSourceSwitching /// - bool ExternalSourceListEnabled { get; } + public interface IHasExternalSourceSwitching + { + /// + /// Gets a value indicating whether the external source list is enabled + /// + bool ExternalSourceListEnabled { get; } - /// - /// Gets the port name for external source input switching - /// - string ExternalSourceInputPort { get; } + /// + /// Gets the external source input port identifier + /// + string ExternalSourceInputPort { get; } + /// + /// Adds an external source to the available sources + /// + /// The connector identifier + /// The unique key for the source + /// The display name for the source + /// The type of external source + void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type); - /// - /// Adds an external source to the list of available sources for switching - /// - /// - /// - /// - /// - void AddExternalSource(string connectorId, string key, string name, eExternalSourceType type); + /// + /// Sets the state of the specified external source + /// + /// The unique key of the external source + /// The mode to set for the source + void SetExternalSourceState(string key, eExternalSourceMode mode); - /// - /// Sets the state of the specified external source - /// - /// The unique key of the external source - /// The mode to set for the source - void SetExternalSourceState(string key, eExternalSourceMode mode); + /// + /// Clears all external sources from the list + /// + void ClearExternalSources(); - /// - /// Clears all external sources from the list - /// - void ClearExternalSources(); + /// + /// Sets the selected source by its key + /// + /// The unique key of the source to select + void SetSelectedSource(string key); - /// - /// Sets the selected external source for switching - /// - /// - void SetSelectedSource(string key); + /// + /// Sets the action to run when routing between sources + /// + Action RunRouteAction { set; } + } - /// - /// Defines an action to run when an external source is selected for switching. - /// The action takes the key and name of the selected source as parameters. - /// - Action RunRouteAction { set; } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs b/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs deleted file mode 100644 index b0d9bad1..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Codec/Interfaces/ICodecCallControls.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; - -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Defines call control functionality for a codec, extending the base dialer interface -/// with active call list access and meeting dialing. -/// -public interface ICodecCallControls : IHasDialer -{ - /// - /// Gets the list of currently active, dialing, or incoming calls - /// - List ActiveCalls { get; } - - /// - /// Dials the specified meeting - /// - /// The meeting to dial - void Dial(Meeting meeting); - - /// - /// Dials the specified number with an optional password - /// - /// The number to dial - /// The optional password for the call - void Dial(string number, string password); -} diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallDirection.cs b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallDirection.cs index 89c852ca..e10f61a3 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallDirection.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallDirection.cs @@ -1,57 +1,57 @@ -namespace PepperDash.Essentials.Devices.Common.Codec; +namespace PepperDash.Essentials.Devices.Common.Codec - -/// -/// Enumeration of eCodecCallDirection values -/// -public enum eCodecCallDirection { /// - /// Unknown call direction + /// Enumeration of eCodecCallDirection values /// - Unknown = 0, - - /// - /// Incoming call direction - /// - Incoming, - - /// - /// Outgoing call direction - /// - Outgoing -} - -/// -/// Represents a CodecCallDirection -/// -public class CodecCallDirection -{ - /// - /// Takes the Cisco call type and converts to the matching enum - /// - /// - /// - /// - /// ConvertToDirectionEnum method - /// - public static eCodecCallDirection ConvertToDirectionEnum(string s) + public enum eCodecCallDirection { - switch (s.ToLower()) + /// + /// Unknown call direction + /// + Unknown = 0, + + /// + /// Incoming call direction + /// + Incoming, + + /// + /// Outgoing call direction + /// + Outgoing + } + + /// + /// Represents a CodecCallDirection + /// + public class CodecCallDirection + { + /// + /// Takes the Cisco call type and converts to the matching enum + /// + /// + /// + /// + /// ConvertToDirectionEnum method + /// + public static eCodecCallDirection ConvertToDirectionEnum(string s) { - case "incoming": - { - return eCodecCallDirection.Incoming; - } - case "outgoing": - { - return eCodecCallDirection.Outgoing; - } - default: - return eCodecCallDirection.Unknown; + switch (s.ToLower()) + { + case "incoming": + { + return eCodecCallDirection.Incoming; + } + case "outgoing": + { + return eCodecCallDirection.Outgoing; + } + default: + return eCodecCallDirection.Unknown; + } + } } - -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallStatus.cs b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallStatus.cs index e9cc89d0..6116ab16 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallStatus.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallStatus.cs @@ -1,138 +1,139 @@ -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Enumeration of eCodecCallStatus values -/// -public enum eCodecCallStatus +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Unknown call status + /// Enumeration of eCodecCallStatus values /// - Unknown = 0, - - /// - /// Call is connected - /// - Connected, - - /// - /// Call is connecting - /// - Connecting, - - /// - /// Call is dialing - /// - Dialing, - - /// - /// Call is disconnected - /// - Disconnected, - - /// - /// Call is disconnecting - /// - Disconnecting, - - /// - /// Early media is being sent/received - /// - EarlyMedia, - - /// - /// Call is idle - /// - Idle, - - /// - /// Call is on hold - /// - OnHold, - - /// - /// Call is ringing - /// - Ringing, - - /// - /// Call is preserved - /// - Preserved, - - /// - /// Call is remote preserved - /// - RemotePreserved, -} - - -/// -/// Represents a CodecCallStatus -/// -public class CodecCallStatus -{ - - /// - /// Takes the Cisco call type and converts to the matching enum - /// - /// - /// - /// - /// ConvertToStatusEnum method - /// - public static eCodecCallStatus ConvertToStatusEnum(string s) + public enum eCodecCallStatus { - switch (s) + /// + /// Unknown call status + /// + Unknown = 0, + + /// + /// Call is connected + /// + Connected, + + /// + /// Call is connecting + /// + Connecting, + + /// + /// Call is dialing + /// + Dialing, + + /// + /// Call is disconnected + /// + Disconnected, + + /// + /// Call is disconnecting + /// + Disconnecting, + + /// + /// Early media is being sent/received + /// + EarlyMedia, + + /// + /// Call is idle + /// + Idle, + + /// + /// Call is on hold + /// + OnHold, + + /// + /// Call is ringing + /// + Ringing, + + /// + /// Call is preserved + /// + Preserved, + + /// + /// Call is remote preserved + /// + RemotePreserved, + } + + + /// + /// Represents a CodecCallStatus + /// + public class CodecCallStatus + { + + /// + /// Takes the Cisco call type and converts to the matching enum + /// + /// + /// + /// + /// ConvertToStatusEnum method + /// + public static eCodecCallStatus ConvertToStatusEnum(string s) { - case "Connected": - { - return eCodecCallStatus.Connected; - } - case "Connecting": - { - return eCodecCallStatus.Connecting; - } - case "Dialling": - { - return eCodecCallStatus.Dialing; - } - case "Disconnected": - { - return eCodecCallStatus.Disconnected; - } - case "Disconnecting": - { - return eCodecCallStatus.Disconnecting; - } - case "EarlyMedia": - { - return eCodecCallStatus.EarlyMedia; - } - case "Idle": - { - return eCodecCallStatus.Idle; - } - case "OnHold": - { - return eCodecCallStatus.OnHold; - } - case "Ringing": - { - return eCodecCallStatus.Ringing; - } - case "Preserved": - { - return eCodecCallStatus.Preserved; - } - case "RemotePreserved": - { - return eCodecCallStatus.RemotePreserved; - } - default: - return eCodecCallStatus.Unknown; + switch (s) + { + case "Connected": + { + return eCodecCallStatus.Connected; + } + case "Connecting": + { + return eCodecCallStatus.Connecting; + } + case "Dialling": + { + return eCodecCallStatus.Dialing; + } + case "Disconnected": + { + return eCodecCallStatus.Disconnected; + } + case "Disconnecting": + { + return eCodecCallStatus.Disconnecting; + } + case "EarlyMedia": + { + return eCodecCallStatus.EarlyMedia; + } + case "Idle": + { + return eCodecCallStatus.Idle; + } + case "OnHold": + { + return eCodecCallStatus.OnHold; + } + case "Ringing": + { + return eCodecCallStatus.Ringing; + } + case "Preserved": + { + return eCodecCallStatus.Preserved; + } + case "RemotePreserved": + { + return eCodecCallStatus.RemotePreserved; + } + default: + return eCodecCallStatus.Unknown; + } + } } -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallType.cs b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallType.cs index 947d0ee2..2b9609fb 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallType.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/eCodecCallType.cs @@ -1,75 +1,76 @@ -namespace PepperDash.Essentials.Devices.Common.Codec; +namespace PepperDash.Essentials.Devices.Common.Codec - -/// -/// Enumeration of eCodecCallType values -/// -public enum eCodecCallType { /// - /// Unknown call type + /// Enumeration of eCodecCallType values /// - Unknown = 0, - - /// - /// Audio-only call type - /// - Audio, - - /// - /// Video call type - /// - Video, - - /// - /// Audio call that can be escalated to video - /// - AudioCanEscalate, - - /// - /// Forward all call type - /// - ForwardAllCall -} - -/// -/// Represents a CodecCallType -/// -public class CodecCallType -{ - - /// - /// Takes the Cisco call type and converts to the matching enum - /// - /// - /// - /// - /// ConvertToTypeEnum method - /// - public static eCodecCallType ConvertToTypeEnum(string s) + public enum eCodecCallType { - switch (s) + /// + /// Unknown call type + /// + Unknown = 0, + + /// + /// Audio-only call type + /// + Audio, + + /// + /// Video call type + /// + Video, + + /// + /// Audio call that can be escalated to video + /// + AudioCanEscalate, + + /// + /// Forward all call type + /// + ForwardAllCall + } + + /// + /// Represents a CodecCallType + /// + public class CodecCallType + { + + /// + /// Takes the Cisco call type and converts to the matching enum + /// + /// + /// + /// + /// ConvertToTypeEnum method + /// + public static eCodecCallType ConvertToTypeEnum(string s) { - case "Audio": - { - return eCodecCallType.Audio; - } - case "Video": - { - return eCodecCallType.Video; - } - case "AudioCanEscalate": - { - return eCodecCallType.AudioCanEscalate; - } - case "ForwardAllCall": - { - return eCodecCallType.ForwardAllCall; - } - default: - return eCodecCallType.Unknown; + switch (s) + { + case "Audio": + { + return eCodecCallType.Audio; + } + case "Video": + { + return eCodecCallType.Video; + } + case "AudioCanEscalate": + { + return eCodecCallType.AudioCanEscalate; + } + case "ForwardAllCall": + { + return eCodecCallType.ForwardAllCall; + } + default: + return eCodecCallType.Unknown; + } + } } - } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/eMeetingPrivacy.cs b/src/PepperDash.Essentials.Devices.Common/Codec/eMeetingPrivacy.cs index b805b738..19a6d31b 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/eMeetingPrivacy.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/eMeetingPrivacy.cs @@ -1,55 +1,56 @@ -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Enumeration of eMeetingPrivacy values -/// -public enum eMeetingPrivacy +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Unknown meeting privacy level + /// Enumeration of eMeetingPrivacy values /// - Unknown = 0, - - /// - /// Public meeting - /// - Public, - - /// - /// Private meeting - /// - Private -} - -/// -/// Represents a CodecCallPrivacy -/// -public class CodecCallPrivacy -{ - /// - /// Takes the Cisco privacy type and converts to the matching enum - /// - /// - /// - /// - /// ConvertToDirectionEnum method - /// - public static eMeetingPrivacy ConvertToDirectionEnum(string s) + public enum eMeetingPrivacy { - switch (s) + /// + /// Unknown meeting privacy level + /// + Unknown = 0, + + /// + /// Public meeting + /// + Public, + + /// + /// Private meeting + /// + Private + } + + /// + /// Represents a CodecCallPrivacy + /// + public class CodecCallPrivacy + { + /// + /// Takes the Cisco privacy type and converts to the matching enum + /// + /// + /// + /// + /// ConvertToDirectionEnum method + /// + public static eMeetingPrivacy ConvertToDirectionEnum(string s) { - case "public": - { - return eMeetingPrivacy.Public; - } - case "private": - { - return eMeetingPrivacy.Private; - } - default: - return eMeetingPrivacy.Unknown; + switch (s.ToLower()) + { + case "public": + { + return eMeetingPrivacy.Public; + } + case "private": + { + return eMeetingPrivacy.Private; + } + default: + return eMeetingPrivacy.Unknown; + } + } } - } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iCodecAudio.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iCodecAudio.cs index 0aa848a4..eeab2801 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iCodecAudio.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iCodecAudio.cs @@ -1,11 +1,12 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Defines minimum volume controls for a codec device with dialing capabilities -/// -public interface ICodecAudio : IBasicVolumeWithFeedback, IPrivacy +namespace PepperDash.Essentials.Devices.Common.Codec { + /// + /// Defines minimum volume controls for a codec device with dialing capabilities + /// + public interface ICodecAudio : IBasicVolumeWithFeedback, IPrivacy + { + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallFavorites.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallFavorites.cs index b28b48cf..eeb6610f 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallFavorites.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallFavorites.cs @@ -1,34 +1,34 @@ using System.Collections.Generic; -namespace PepperDash.Essentials.Devices.Common.Codec; - - -/// -/// Defines the contract for IHasCallFavorites -/// -public interface IHasCallFavorites +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Gets the call favorites for this device + /// Defines the contract for IHasCallFavorites /// - CodecCallFavorites CallFavorites { get; } -} - -/// -/// Represents a CodecCallFavorites -/// -public class CodecCallFavorites -{ - /// - /// Gets or sets the Favorites - /// - public List Favorites { get; set; } - - /// - /// Initializes a new instance of the CodecCallFavorites class - /// - public CodecCallFavorites() + public interface IHasCallFavorites { - Favorites = new List(); + /// + /// Gets the call favorites for this device + /// + CodecCallFavorites CallFavorites { get; } } -} + + /// + /// Represents a CodecCallFavorites + /// + public class CodecCallFavorites + { + /// + /// Gets or sets the Favorites + /// + public List Favorites { get; set; } + + /// + /// Initializes a new instance of the CodecCallFavorites class + /// + public CodecCallFavorites() + { + Favorites = new List(); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallHistory.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallHistory.cs index 025e4a12..7f495daa 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallHistory.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iHasCallHistory.cs @@ -6,131 +6,188 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PepperDash.Essentials.Devices.Common.VideoCodec; -namespace PepperDash.Essentials.Devices.Common.Codec; - - -public interface IHasCallHistory +namespace PepperDash.Essentials.Devices.Common.Codec { - CodecCallHistory CallHistory { get; } - - void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry); -} - -public enum eCodecOccurrenceType -{ - Unknown = 0, - Placed = 1, - Received = 2, - NoAnswer = 3, -} - -/// -/// Represents the recent call history for a codec device -/// -public class CodecCallHistory -{ - public event EventHandler RecentCallsListHasChanged; - - public List RecentCalls { get; private set; } /// - /// Item that gets added to the list when there are no recent calls in history + /// Defines the contract for IHasCallHistory /// - CallHistoryEntry ListEmptyEntry; - - public CodecCallHistory() + public interface IHasCallHistory { - ListEmptyEntry = new CallHistoryEntry() { Name = "No Recent Calls" }; + /// + /// Gets the call history for this device + /// + CodecCallHistory CallHistory { get; } - RecentCalls = new List(); - - RecentCalls.Add(ListEmptyEntry); + /// + /// Removes the specified call history entry + /// + /// The call history entry to remove + void RemoveCallHistoryEntry(CodecCallHistory.CallHistoryEntry entry); } - void OnRecentCallsListChange() + /// + /// Enumeration of eCodecOccurrenceType values + /// + public enum eCodecOccurrenceType { - var handler = RecentCallsListHasChanged; - if (handler != null) + /// + /// Unknown occurrence type + /// + Unknown = 0, + + /// + /// Call was placed (outgoing) + /// + Placed = 1, + + /// + /// Call was received (incoming) + /// + Received = 2, + + /// + /// Call received no answer + /// + NoAnswer = 3, + } + + /// + /// Represents a CodecCallHistory + /// + public class CodecCallHistory + { + /// + /// Event that is raised when the recent calls list has changed + /// + public event EventHandler RecentCallsListHasChanged; + + /// + /// Gets or sets the RecentCalls + /// + public List RecentCalls { get; private set; } + + /// + /// Item that gets added to the list when there are no recent calls in history + /// + CallHistoryEntry ListEmptyEntry; + + /// + /// Initializes a new instance of the CodecCallHistory class + /// + public CodecCallHistory() { - handler(this, new EventArgs()); + ListEmptyEntry = new CallHistoryEntry() { Name = "No Recent Calls" }; + + RecentCalls = new List(); + + RecentCalls.Add(ListEmptyEntry); } - } - public void RemoveEntry(CallHistoryEntry entry) - { - RecentCalls.Remove(entry); - OnRecentCallsListChange(); - } - - /// - /// Generic call history entry, not device specific - /// - public class CallHistoryEntry : CodecActiveCallItem - { - [JsonConverter(typeof(IsoDateTimeConverter))] - [JsonProperty("startTime")] - public DateTime StartTime { get; set; } - [JsonConverter(typeof(StringEnumConverter))] - [JsonProperty("occurrenceType")] - public eCodecOccurrenceType OccurrenceType { get; set; } - [JsonProperty("occurrenceHistoryId")] - public string OccurrenceHistoryId { get; set; } - } - - /// - /// Converts a list of call history entries returned by a Cisco codec to the generic list type - /// - /// - /// - public void ConvertCiscoCallHistoryToGeneric(List entries) - { - var genericEntries = new List(); - - foreach (CiscoCallHistory.Entry entry in entries) + void OnRecentCallsListChange() { - - genericEntries.Add(new CallHistoryEntry() + var handler = RecentCallsListHasChanged; + if (handler != null) { - Name = entry.DisplayName.Value, - Number = entry.CallbackNumber.Value, - StartTime = entry.LastOccurrenceStartTime.Value, - OccurrenceHistoryId = entry.LastOccurrenceHistoryId.Value, - OccurrenceType = ConvertToOccurenceTypeEnum(entry.OccurrenceType.Value) - }); + handler(this, new EventArgs()); + } } - // Check if list is empty and if so, add an item to display No Recent Calls - if(genericEntries.Count == 0) - genericEntries.Add(ListEmptyEntry); - - RecentCalls = genericEntries; - OnRecentCallsListChange(); - } - - /// - /// Takes the Cisco occurence type and converts it to the matching enum - /// - /// - public eCodecOccurrenceType ConvertToOccurenceTypeEnum(string s) - { - switch (s) + /// + /// RemoveEntry method + /// + public void RemoveEntry(CallHistoryEntry entry) { - case "Placed": + RecentCalls.Remove(entry); + OnRecentCallsListChange(); + } + + /// + /// Represents a CallHistoryEntry + /// + public class CallHistoryEntry : CodecActiveCallItem + { + /// + /// Gets or sets the StartTime + /// + [JsonConverter(typeof(IsoDateTimeConverter))] + [JsonProperty("startTime")] + public DateTime StartTime { get; set; } + /// + /// Gets or sets the occurrence type for this call history entry + /// + [JsonConverter(typeof(StringEnumConverter))] + [JsonProperty("occurrenceType")] + public eCodecOccurrenceType OccurrenceType { get; set; } + + /// + /// Gets or sets the occurrence history identifier + /// + [JsonProperty("occurrenceHistoryId")] + public string OccurrenceHistoryId { get; set; } + } + + /// + /// Converts a list of call history entries returned by a Cisco codec to the generic list type + /// + /// + /// + /// + /// ConvertCiscoCallHistoryToGeneric method + /// + public void ConvertCiscoCallHistoryToGeneric(List entries) + { + var genericEntries = new List(); + + foreach (CiscoCallHistory.Entry entry in entries) + { + + genericEntries.Add(new CallHistoryEntry() { - return eCodecOccurrenceType.Placed; - } - case "Received": - { - return eCodecOccurrenceType.Received; - } - case "NoAnswer": - { - return eCodecOccurrenceType.NoAnswer; - } - default: - return eCodecOccurrenceType.Unknown; + Name = entry.DisplayName.Value, + Number = entry.CallbackNumber.Value, + StartTime = entry.LastOccurrenceStartTime.Value, + OccurrenceHistoryId = entry.LastOccurrenceHistoryId.Value, + OccurrenceType = ConvertToOccurenceTypeEnum(entry.OccurrenceType.Value) + }); + } + + // Check if list is empty and if so, add an item to display No Recent Calls + if (genericEntries.Count == 0) + genericEntries.Add(ListEmptyEntry); + + RecentCalls = genericEntries; + OnRecentCallsListChange(); + } + + /// + /// Takes the Cisco occurence type and converts it to the matching enum + /// + /// + /// + /// ConvertToOccurenceTypeEnum method + /// + public eCodecOccurrenceType ConvertToOccurenceTypeEnum(string s) + { + switch (s) + { + case "Placed": + { + return eCodecOccurrenceType.Placed; + } + case "Received": + { + return eCodecOccurrenceType.Received; + } + case "NoAnswer": + { + return eCodecOccurrenceType.NoAnswer; + } + default: + return eCodecOccurrenceType.Unknown; + } + } } - } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iHasContentSharing.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iHasContentSharing.cs index e70c1db6..2dea5b4a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iHasContentSharing.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iHasContentSharing.cs @@ -1,35 +1,37 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Defines the contract for IHasContentSharing -/// -public interface IHasContentSharing +namespace PepperDash.Essentials.Devices.Common.Codec { /// - /// Gets feedback indicating whether content sharing is currently active + /// Defines the contract for IHasContentSharing /// - BoolFeedback SharingContentIsOnFeedback { get; } + public interface IHasContentSharing + { + /// + /// Gets feedback indicating whether content sharing is currently active + /// + BoolFeedback SharingContentIsOnFeedback { get; } - /// - /// Gets feedback about the current sharing source - /// - StringFeedback SharingSourceFeedback { get; } + /// + /// Gets feedback about the current sharing source + /// + StringFeedback SharingSourceFeedback { get; } - /// - /// Gets a value indicating whether content should be automatically shared while in a call - /// - bool AutoShareContentWhileInCall { get; } + /// + /// Gets a value indicating whether content should be automatically shared while in a call + /// + bool AutoShareContentWhileInCall { get; } - /// - /// Starts content sharing - /// - void StartSharing(); + /// + /// Starts content sharing + /// + void StartSharing(); - /// - /// Stops content sharing - /// - void StopSharing(); -} + /// + /// Stops content sharing + /// + void StopSharing(); + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs b/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs index 95371404..a5373004 100644 --- a/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs +++ b/src/PepperDash.Essentials.Devices.Common/Codec/iHasDialer.cs @@ -1,59 +1,58 @@ using System; -namespace PepperDash.Essentials.Devices.Common.Codec; - -/// -/// Requirements for a device that has dialing capabilities -/// -public interface IHasDialer +namespace PepperDash.Essentials.Devices.Common.Codec { - // Add requirements for Dialer functionality - /// - /// Event that is raised when call status changes + /// Requirements for a device that has dialing capabilities /// - event EventHandler CallStatusChange; + public interface IHasDialer + { + // Add requirements for Dialer functionality - /// - /// Dials the specified number - /// - /// The number to dial - void Dial(string number); + /// + /// Event that is raised when call status changes + /// + event EventHandler CallStatusChange; - /// - /// Ends the specified active call - /// - /// The active call to end - void EndCall(CodecActiveCallItem activeCall); + /// + /// Dials the specified number + /// + /// The number to dial + void Dial(string number); - /// - /// Ends all active calls - /// - void EndAllCalls(); + /// + /// Ends the specified active call + /// + /// The active call to end + void EndCall(CodecActiveCallItem activeCall); - /// - /// Accepts the specified incoming call - /// - /// The call item to accept - void AcceptCall(CodecActiveCallItem item); + /// + /// Ends all active calls + /// + void EndAllCalls(); - /// - /// Rejects the specified incoming call - /// - /// The call item to reject - void RejectCall(CodecActiveCallItem item); + /// + /// Accepts the specified incoming call + /// + /// The call item to accept + void AcceptCall(CodecActiveCallItem item); - /// - /// Sends DTMF digits during a call - /// - /// The DTMF digit(s) to send - void SendDtmf(string digit); + /// + /// Rejects the specified incoming call + /// + /// The call item to reject + void RejectCall(CodecActiveCallItem item); - /// - /// Gets a value indicating whether the device is currently in a call - /// - bool IsInCall { get; } + /// + /// Sends DTMF digits during a call + /// + /// The DTMF digit(s) to send + void SendDtmf(string digit); - -} + /// + /// Gets a value indicating whether the device is currently in a call + /// + bool IsInCall { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/DSP/DspBase.cs b/src/PepperDash.Essentials.Devices.Common/DSP/DspBase.cs index 3ea3ddde..051f3b1a 100644 --- a/src/PepperDash.Essentials.Devices.Common/DSP/DspBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/DSP/DspBase.cs @@ -4,147 +4,153 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -namespace PepperDash.Essentials.Devices.Common.DSP; - -/// -/// Base class for DSP devices -/// -public abstract class DspBase : EssentialsDevice, ILevelControls +namespace PepperDash.Essentials.Devices.Common.DSP { /// - /// Gets the collection of level control points + /// Base class for DSP devices /// - public Dictionary LevelControlPoints { get; private set; } - - /// - /// Gets the collection of dialer control points - /// - public Dictionary DialerControlPoints { get; private set; } - - /// - /// Gets the collection of switcher control points - /// - public Dictionary SwitcherControlPoints { get; private set; } - - /// - /// Initializes a new instance of the DspBase class - /// - /// The device key - /// The device name - public DspBase(string key, string name) : - base(key, name) + public abstract class DspBase : EssentialsDevice, ILevelControls { + /// + /// Gets the collection of level control points + /// + public Dictionary LevelControlPoints { get; private set; } + + /// + /// Gets the collection of dialer control points + /// + public Dictionary DialerControlPoints { get; private set; } + + /// + /// Gets the collection of switcher control points + /// + public Dictionary SwitcherControlPoints { get; private set; } + + /// + /// Initializes a new instance of the DspBase class + /// + /// The device key + /// The device name + public DspBase(string key, string name) : + base(key, name) + { + + LevelControlPoints = new Dictionary(); + DialerControlPoints = new Dictionary(); + SwitcherControlPoints = new Dictionary(); + } + + + // in audio call feedback + + // VOIP + // Phone dialer - LevelControlPoints = new Dictionary(); - DialerControlPoints = new Dictionary(); - SwitcherControlPoints = new Dictionary(); } + // Fusion + // Privacy state + // Online state + // level/mutes ? - // in audio call feedback + // AC Log call stats - // VOIP - // Phone dialer - -} - -// Fusion -// Privacy state -// Online state -// level/mutes ? - -// AC Log call stats - -// Typical presets: -// call default preset to restore levels and mutes - -/// -/// Base class for DSP control points -/// -public abstract class DspControlPoint : IKeyName -{ - /// - /// Gets or sets the Key - /// - public string Key { get; } + // Typical presets: + // call default preset to restore levels and mutes /// - /// Gets or sets the Name + /// Base class for DSP control points /// - public string Name { get; private set; } - - /// - /// Initializes a new instance of the DspControlPoint class - /// - /// The control point key - protected DspControlPoint(string key) => Key = key; -} - -/// -/// Base class for DSP level control points with volume and mute functionality -/// -public abstract class DspLevelControlPoint : DspControlPoint, IBasicVolumeWithFeedback -{ - /// - /// Gets or sets the MuteFeedback - /// - public BoolFeedback MuteFeedback { get; } - /// - /// Gets or sets the VolumeLevelFeedback - /// - public IntFeedback VolumeLevelFeedback { get; } - - /// - /// Initializes a new instance of the DspLevelControlPoint class - /// - /// The control point key - /// Function to get mute status - /// Function to get volume level - protected DspLevelControlPoint(string key, Func muteFeedbackFunc, Func volumeLevelFeedbackFunc) : base(key) + public abstract class DspControlPoint : IKeyName { - MuteFeedback = new BoolFeedback("mute", muteFeedbackFunc); - VolumeLevelFeedback = new IntFeedback("volume", volumeLevelFeedbackFunc); + /// + /// Gets or sets the Key + /// + public string Key { get; } + + /// + /// Gets or sets the Name + /// + public string Name { get; private set; } + + /// + /// Initializes a new instance of the DspControlPoint class + /// + /// The control point key + protected DspControlPoint(string key) => Key = key; } /// - /// Turns mute off + /// Base class for DSP level control points with volume and mute functionality /// - public abstract void MuteOff(); - /// - /// Turns mute on - /// - public abstract void MuteOn(); - /// - /// Toggles mute state - /// - public abstract void MuteToggle(); - /// - /// Sets the volume level - /// - /// The volume level to set - public abstract void SetVolume(ushort level); - /// - /// Decreases volume - /// - /// True when pressed, false when released - public abstract void VolumeDown(bool pressRelease); - /// - /// Increases volume - /// - /// True when pressed, false when released - public abstract void VolumeUp(bool pressRelease); -} + public abstract class DspLevelControlPoint : DspControlPoint, IBasicVolumeWithFeedback + { + /// + /// Gets or sets the MuteFeedback + /// + public BoolFeedback MuteFeedback { get; } + /// + /// Gets or sets the VolumeLevelFeedback + /// + public IntFeedback VolumeLevelFeedback { get; } + + /// + /// Initializes a new instance of the DspLevelControlPoint class + /// + /// The control point key + /// Function to get mute status + /// Function to get volume level + protected DspLevelControlPoint(string key, Func muteFeedbackFunc, Func volumeLevelFeedbackFunc) : base(key) + { + MuteFeedback = new BoolFeedback("mute", muteFeedbackFunc); + VolumeLevelFeedback = new IntFeedback("volume", volumeLevelFeedbackFunc); + } + + /// + /// Turns mute off + /// + public abstract void MuteOff(); + /// + /// Turns mute on + /// + public abstract void MuteOn(); + /// + /// Toggles mute state + /// + public abstract void MuteToggle(); + /// + /// Sets the volume level + /// + /// The volume level to set + public abstract void SetVolume(ushort level); + /// + /// Decreases volume + /// + /// True when pressed, false when released + public abstract void VolumeDown(bool pressRelease); + /// + /// Increases volume + /// + /// True when pressed, false when released + public abstract void VolumeUp(bool pressRelease); + } -/// -/// Base class for DSP dialer control points -/// -public abstract class DspDialerBase : DspControlPoint -{ /// - /// Initializes a new instance of the DspDialerBase class + /// Base class for DSP dialer control points /// - /// The dialer control point key - protected DspDialerBase(string key) : base(key) { } -} + public abstract class DspDialerBase : DspControlPoint + { + /// + /// Initializes a new instance of the DspDialerBase class + /// + /// The dialer control point key + protected DspDialerBase(string key) : base(key) { } + } + // Main program + // VTC + // ATC + // Mics, unusual + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs index 53f3927f..2c7507ba 100644 --- a/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs +++ b/src/PepperDash.Essentials.Devices.Common/DeviceFactory.cs @@ -7,51 +7,58 @@ using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common; - -public class DeviceFactory +namespace PepperDash.Essentials.Devices.Common { - - public DeviceFactory() + /// + /// Represents a DeviceFactory + /// + public class DeviceFactory { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); - - var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types != null) + /// + /// Initializes a new instance of the DeviceFactory class + /// + public DeviceFactory() { - foreach (var type in types) + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); + + var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (types != null) { - try + foreach (var type in types) { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - LoadDeviceFactories(factory); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); + try + { + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load type: '{1}' DeviceFactory: {0}", e, type.Name); + } } } } - } - /// - /// Loads device factories from the specified plugin device factory and registers them for use. - /// - /// This method retrieves metadata from the provided , including - /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type - /// names are converted to lowercase for registration. - /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. - private static void LoadDeviceFactories(IDeviceFactory deviceFactory) - { - foreach (var typeName in deviceFactory.TypeNames) + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + foreach (var typeName in deviceFactory.TypeNames) + { + //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; + + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs b/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs index 9c8137d7..858cdf42 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/BasicIrDisplay.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; @@ -9,66 +9,65 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Displays; - -/// -/// Represents a basic IR controlled display device. This class is intended to be used for simple displays that are controlled via IR and do not have a more robust API. It provides basic power and volume controls, as well as input switching via IR commands. -/// -public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced +namespace PepperDash.Essentials.Devices.Common.Displays { /// - /// Gets or sets the IrPort + /// Represents a BasicIrDisplay /// - public IrOutputPortController IrPort { get; private set; } - /// - /// Gets or sets the IrPulseTime - /// - public ushort IrPulseTime { get; set; } - - /// - /// Gets the power is on feedback function - /// - protected Func PowerIsOnFeedbackFunc + public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced { - get { return () => _PowerIsOn; } - } + /// + /// Gets or sets the IrPort + /// + public IrOutputPortController IrPort { get; private set; } + /// + /// Gets or sets the IrPulseTime + /// + public ushort IrPulseTime { get; set; } - /// - /// Gets the is cooling down feedback function - /// - protected override Func IsCoolingDownFeedbackFunc - { - get { return () => _IsCoolingDown; } - } - /// - /// Gets the is warming up feedback function - /// - protected override Func IsWarmingUpFeedbackFunc - { - get { return () => _IsWarmingUp; } - } + /// + /// Gets the power is on feedback function + /// + protected Func PowerIsOnFeedbackFunc + { + get { return () => _PowerIsOn; } + } + /// + /// Gets the is cooling down feedback function + /// + protected override Func IsCoolingDownFeedbackFunc + { + get { return () => _IsCoolingDown; } + } + /// + /// Gets the is warming up feedback function + /// + protected override Func IsWarmingUpFeedbackFunc + { + get { return () => _IsWarmingUp; } + } - bool _PowerIsOn; - bool _IsWarmingUp; - bool _IsCoolingDown; + bool _PowerIsOn; + bool _IsWarmingUp; + bool _IsCoolingDown; - /// - /// Initializes a new instance of the BasicIrDisplay class - /// - /// The device key - /// The device name - /// The IR output port - /// The path to the IR driver file - public BasicIrDisplay(string key, string name, IROutputPort port, string irDriverFilepath) - : base(key, name) - { - IrPort = new IrOutputPortController(key + "-ir", port, irDriverFilepath); - DeviceManager.AddDevice(IrPort); + /// + /// Initializes a new instance of the BasicIrDisplay class + /// + /// The device key + /// The device name + /// The IR output port + /// The path to the IR driver file + public BasicIrDisplay(string key, string name, IROutputPort port, string irDriverFilepath) + : base(key, name) + { + IrPort = new IrOutputPortController(key + "-ir", port, irDriverFilepath); + DeviceManager.AddDevice(IrPort); - IsWarmingUpFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Verbose, this, "Warming up={0}", _IsWarmingUp); - IsCoolingDownFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Verbose, this, "Cooling down={0}", _IsCoolingDown); + IsWarmingUpFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Verbose, this, "Warming up={0}", _IsWarmingUp); + IsCoolingDownFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Verbose, this, "Cooling down={0}", _IsCoolingDown); - InputPorts.AddRange(new RoutingPortCollection + InputPorts.AddRange(new RoutingPortCollection { new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(Hdmi1), this, false), @@ -85,227 +84,224 @@ public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced new RoutingInputPort(RoutingPortNames.AntennaIn, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, new Action(Antenna), this, false), }); - } + } - /// - /// Hdmi1 method - /// - public void Hdmi1() - { - IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_1, IrPulseTime); - } - - /// - /// Hdmi2 method - /// - public void Hdmi2() - { - IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_2, IrPulseTime); - } - - /// - /// Hdmi3 method - /// - public void Hdmi3() - { - IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_3, IrPulseTime); - } - - /// - /// Hdmi4 method - /// - public void Hdmi4() - { - IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_4, IrPulseTime); - } - - /// - /// Component1 method - /// - public void Component1() - { - IrPort.Pulse(IROutputStandardCommands.IROut_COMPONENT_1, IrPulseTime); - } - - /// - /// Video1 method - /// - public void Video1() - { - IrPort.Pulse(IROutputStandardCommands.IROut_VIDEO_1, IrPulseTime); - } - - /// - /// Antenna method - /// - public void Antenna() - { - IrPort.Pulse(IROutputStandardCommands.IROut_ANTENNA, IrPulseTime); - } - - #region IPower Members - - /// - /// PowerOn method - /// - /// - public override void PowerOn() - { - IrPort.Pulse(IROutputStandardCommands.IROut_POWER_ON, IrPulseTime); - _PowerIsOn = true; - } - - /// - /// PowerOff method - /// - public override void PowerOff() - { - _PowerIsOn = false; - IrPort.Pulse(IROutputStandardCommands.IROut_POWER_OFF, IrPulseTime); - } - - /// - /// PowerToggle method - /// - public override void PowerToggle() - { - _PowerIsOn = false; - IrPort.Pulse(IROutputStandardCommands.IROut_POWER, IrPulseTime); - } - - #endregion - - #region IBasicVolumeControls Members - - /// - /// VolumeUp method - /// - public void VolumeUp(bool pressRelease) - { - IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_PLUS, pressRelease); - } - - /// - /// VolumeDown method - /// - public void VolumeDown(bool pressRelease) - { - IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_MINUS, pressRelease); - } - - /// - /// MuteToggle method - /// - public void MuteToggle() - { - IrPort.Pulse(IROutputStandardCommands.IROut_MUTE, 200); - } - - #endregion - - void StartWarmingTimer() - { - _IsWarmingUp = true; - IsWarmingUpFeedback.FireUpdate(); - var t = new Timer(10000) { AutoReset = false }; - t.Elapsed += (s, e) => + /// + /// Hdmi1 method + /// + public void Hdmi1() { - _IsWarmingUp = false; + IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_1, IrPulseTime); + } + + /// + /// Hdmi2 method + /// + public void Hdmi2() + { + IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_2, IrPulseTime); + } + + /// + /// Hdmi3 method + /// + public void Hdmi3() + { + IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_3, IrPulseTime); + } + + /// + /// Hdmi4 method + /// + public void Hdmi4() + { + IrPort.Pulse(IROutputStandardCommands.IROut_HDMI_4, IrPulseTime); + } + + /// + /// Component1 method + /// + public void Component1() + { + IrPort.Pulse(IROutputStandardCommands.IROut_COMPONENT_1, IrPulseTime); + } + + /// + /// Video1 method + /// + public void Video1() + { + IrPort.Pulse(IROutputStandardCommands.IROut_VIDEO_1, IrPulseTime); + } + + /// + /// Antenna method + /// + public void Antenna() + { + IrPort.Pulse(IROutputStandardCommands.IROut_ANTENNA, IrPulseTime); + } + + #region IPower Members + + /// + /// PowerOn method + /// + /// + public override void PowerOn() + { + IrPort.Pulse(IROutputStandardCommands.IROut_POWER_ON, IrPulseTime); + _PowerIsOn = true; + } + + /// + /// PowerOff method + /// + public override void PowerOff() + { + _PowerIsOn = false; + IrPort.Pulse(IROutputStandardCommands.IROut_POWER_OFF, IrPulseTime); + } + + /// + /// PowerToggle method + /// + public override void PowerToggle() + { + _PowerIsOn = false; + IrPort.Pulse(IROutputStandardCommands.IROut_POWER, IrPulseTime); + } + + #endregion + + #region IBasicVolumeControls Members + + /// + /// VolumeUp method + /// + public void VolumeUp(bool pressRelease) + { + IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_PLUS, pressRelease); + } + + /// + /// VolumeDown method + /// + public void VolumeDown(bool pressRelease) + { + IrPort.PressRelease(IROutputStandardCommands.IROut_VOL_MINUS, pressRelease); + } + + /// + /// MuteToggle method + /// + public void MuteToggle() + { + IrPort.Pulse(IROutputStandardCommands.IROut_MUTE, 200); + } + + #endregion + + void StartWarmingTimer() + { + _IsWarmingUp = true; IsWarmingUpFeedback.FireUpdate(); - }; - t.Start(); - } - - void StartCoolingTimer() - { - _IsCoolingDown = true; - IsCoolingDownFeedback.FireUpdate(); - var t = new Timer(7000) { AutoReset = false }; - t.Elapsed += (s, e) => - { - _IsCoolingDown = false; - IsCoolingDownFeedback.FireUpdate(); - }; - t.Start(); - } - - #region IRoutingSinkWithFeedback Members - - /// - /// Typically called by the discovery routing algorithm. - /// - /// A delegate containing the input selector method to call - /// - /// ExecuteSwitch method - /// - /// - public override void ExecuteSwitch(object inputSelector) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Switching to input '{0}'", (inputSelector as Action).ToString()); - - Action finishSwitch = () => + new CTimer(o => { - var action = inputSelector as Action; - if (action != null) - action(); - }; - - if (!_PowerIsOn) - { - PowerOn(); - EventHandler oneTimer = null; - oneTimer = (o, a) => - { - if (IsWarmingUpFeedback.BoolValue) return; // Only catch done warming - IsWarmingUpFeedback.OutputChange -= oneTimer; - finishSwitch(); - }; - IsWarmingUpFeedback.OutputChange += oneTimer; - } - else // Do it! - finishSwitch(); - } - - #endregion - - /// - /// LinkToApi method - /// - public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); - } -} - -/// -/// Factory for creating BasicIrDisplay devices -/// -public class BasicIrDisplayFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the BasicIrDisplayFactory class - /// - public BasicIrDisplayFactory() - { - TypeNames = new List() { "basicirdisplay" }; - } - - /// - /// BuildDevice method - /// - /// - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new BasicIrDisplay Device"); - var ir = IRPortHelper.GetIrPort(dc.Properties); - if (ir != null) - { - var display = new BasicIrDisplay(dc.Key, dc.Name, ir.Port, ir.FileName); - display.IrPulseTime = 200; // Set default pulse time for IR commands. - return display; + _IsWarmingUp = false; + IsWarmingUpFeedback.FireUpdate(); + }, 10000); } - return null; + void StartCoolingTimer() + { + _IsCoolingDown = true; + IsCoolingDownFeedback.FireUpdate(); + new CTimer(o => + { + _IsCoolingDown = false; + IsCoolingDownFeedback.FireUpdate(); + }, 7000); + } + + #region IRoutingSink Members + + /// + /// Typically called by the discovery routing algorithm. + /// + /// A delegate containing the input selector method to call + /// + /// ExecuteSwitch method + /// + /// + public override void ExecuteSwitch(object inputSelector) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Switching to input '{0}'", (inputSelector as Action).ToString()); + + Action finishSwitch = () => + { + var action = inputSelector as Action; + if (action != null) + action(); + }; + + if (!_PowerIsOn) + { + PowerOn(); + EventHandler oneTimer = null; + oneTimer = (o, a) => + { + if (IsWarmingUpFeedback.BoolValue) return; // Only catch done warming + IsWarmingUpFeedback.OutputChange -= oneTimer; + finishSwitch(); + }; + IsWarmingUpFeedback.OutputChange += oneTimer; + } + else // Do it! + finishSwitch(); + } + + #endregion + + /// + /// LinkToApi method + /// + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); + } } + + /// + /// Represents a BasicIrDisplayFactory + /// + public class BasicIrDisplayFactory : EssentialsDeviceFactory + { + /// + /// Initializes a new instance of the BasicIrDisplayFactory class + /// + public BasicIrDisplayFactory() + { + TypeNames = new List() { "basicirdisplay" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new BasicIrDisplay Device"); + var ir = IRPortHelper.GetIrPort(dc.Properties); + if (ir != null) + { + var display = new BasicIrDisplay(dc.Key, dc.Name, ir.Port, ir.FileName); + display.IrPulseTime = 200; // Set default pulse time for IR commands. + return display; + } + + return null; + } + } + } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs index 6662ee75..5db03a30 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/DisplayBase.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; using Newtonsoft.Json; using PepperDash.Core; @@ -13,403 +13,426 @@ using PepperDash.Essentials.Core.Routing; using Serilog.Events; using Feedback = PepperDash.Essentials.Core.Feedback; -namespace PepperDash.Essentials.Devices.Common.Displays; - -/// -/// Abstract base class for display devices that provides common display functionality -/// including power control, input switching, and routing capabilities. -/// -public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources, IHasFeedback +namespace PepperDash.Essentials.Devices.Common.Displays { - private RoutingInputPort _currentInputPort; - /// - /// Gets or sets the current input port that is selected on the display. + /// Abstract base class for display devices that provides common display functionality + /// including power control, input switching, and routing capabilities. /// - public RoutingInputPort CurrentInputPort + public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources, IHasFeedback { - get + private RoutingInputPort _currentInputPort; + + /// + /// Gets or sets the current input port that is selected on the display. + /// + public RoutingInputPort CurrentInputPort { - return _currentInputPort; + get + { + return _currentInputPort; + } + + protected set + { + if (_currentInputPort == value) return; + + _currentInputPort = value; + + InputChanged?.Invoke(this, _currentInputPort); + } } - protected set + /// + /// Event that is raised when the input changes on the display. + /// + public event InputChangedEventHandler InputChanged; + + /// + /// Event that is raised when the current source information changes. + /// + public event SourceInfoChangeHandler CurrentSourceChange; + + /// + /// Gets or sets the CurrentSourceInfoKey + /// + public string CurrentSourceInfoKey { get; set; } + + /// + /// Gets or sets the current source information for the display. + /// + public SourceListItem CurrentSourceInfo { - if (_currentInputPort == value) return; + get + { + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; - _currentInputPort = value; + var handler = CurrentSourceChange; - InputChanged?.Invoke(this, _currentInputPort); + handler?.Invoke(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + handler?.Invoke(_CurrentSourceInfo, ChangeType.DidChange); + } } - } + SourceListItem _CurrentSourceInfo; - /// - /// Event that is raised when the input changes on the display. - /// - public event InputChangedEventHandler InputChanged; + /// + public Dictionary CurrentSources { get; private set; } - /// - /// Gets or sets the CurrentSourceInfoKey - /// - public string CurrentSourceInfoKey { get; set; } + /// + public Dictionary CurrentSourceKeys { get; private set; } + /// + public event EventHandler CurrentSourcesChanged; - /// - public Dictionary CurrentSources { get; private set; } + /// + /// Gets feedback indicating whether the display is currently cooling down after being powered off. + /// + public BoolFeedback IsCoolingDownFeedback { get; protected set; } - /// - public Dictionary CurrentSourceKeys { get; private set; } + /// + /// Gets or sets the IsWarmingUpFeedback + /// + public BoolFeedback IsWarmingUpFeedback { get; private set; } - /// - public event EventHandler CurrentSourcesChanged; + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } - /// - /// Gets feedback indicating whether the display is currently cooling down after being powered off. - /// - public BoolFeedback IsCoolingDownFeedback { get; protected set; } + /// + /// Gets or sets the WarmupTime + /// + public uint WarmupTime { get; set; } - /// - /// Gets or sets the IsWarmingUpFeedback - /// - public BoolFeedback IsWarmingUpFeedback { get; private set; } + /// + /// Gets or sets the CooldownTime + /// + public uint CooldownTime { get; set; } - /// - /// Gets or sets the UsageTracker - /// - public UsageTracking UsageTracker { get; set; } + /// + /// Abstract function that must be implemented by derived classes to provide the cooling down feedback value. + /// Must be implemented by concrete sub-classes. + /// + abstract protected Func IsCoolingDownFeedbackFunc { get; } - /// - /// Gets or sets the WarmupTime - /// - public uint WarmupTime { get; set; } + /// + /// Abstract function that must be implemented by derived classes to provide the warming up feedback value. + /// Must be implemented by concrete sub-classes. + /// + abstract protected Func IsWarmingUpFeedbackFunc { get; } - /// - /// Gets or sets the CooldownTime - /// - public uint CooldownTime { get; set; } + /// + /// Timer used for managing display warmup timing. + /// + protected CTimer WarmupTimer; - /// - /// Abstract function that must be implemented by derived classes to provide the cooling down feedback value. - /// Must be implemented by concrete sub-classes. - /// - abstract protected Func IsCoolingDownFeedbackFunc { get; } + /// + /// Timer used for managing display cooldown timing. + /// + protected CTimer CooldownTimer; - /// - /// Abstract function that must be implemented by derived classes to provide the warming up feedback value. - /// Must be implemented by concrete sub-classes. - /// - abstract protected Func IsWarmingUpFeedbackFunc { get; } + #region IRoutingInputs Members - /// - /// Timer used for managing display warmup timing. - /// - protected Timer WarmupTimer; + /// + /// Gets the collection of input ports available on this display device. + /// + public RoutingPortCollection InputPorts { get; private set; } - /// - /// Timer used for managing display cooldown timing. - /// - protected Timer CooldownTimer; + #endregion - #region IRoutingInputs Members + /// + /// Initializes a new instance of the DisplayBase class. + /// + /// The unique key identifier for this display device. + /// The friendly name for this display device. + protected DisplayBase(string key, string name) + : base(key, name) + { + IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc); + IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc); - /// - /// Gets the collection of input ports available on this display device. - /// - public RoutingPortCollection InputPorts { get; private set; } + Feedbacks.Add(IsCoolingDownFeedback); + Feedbacks.Add(IsWarmingUpFeedback); - #endregion + InputPorts = new RoutingPortCollection(); - /// - /// Initializes a new instance of the DisplayBase class. - /// - /// The unique key identifier for this display device. - /// The friendly name for this display device. - protected DisplayBase(string key, string name) - : base(key, name) - { - IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc); - IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc); - - Feedbacks.Add(IsCoolingDownFeedback); - Feedbacks.Add(IsWarmingUpFeedback); - - InputPorts = new RoutingPortCollection(); - - CurrentSources = new Dictionary + CurrentSources = new Dictionary { { eRoutingSignalType.Audio, null }, { eRoutingSignalType.Video, null }, }; - CurrentSourceKeys = new Dictionary + CurrentSourceKeys = new Dictionary { { eRoutingSignalType.Audio, string.Empty }, { eRoutingSignalType.Video, string.Empty }, }; - } - - /// - /// Powers on the display device. Must be implemented by derived classes. - /// - public abstract void PowerOn(); - - /// - /// Powers off the display device. Must be implemented by derived classes. - /// - public abstract void PowerOff(); - - /// - /// Toggles the power state of the display device. Must be implemented by derived classes. - /// - public abstract void PowerToggle(); - - /// - /// Gets the collection of feedback objects for this display device. - /// - /// - public virtual FeedbackCollection Feedbacks { get; private set; } = new FeedbackCollection(); - - - /// - /// Executes a switch to the specified input on the display device. Must be implemented by derived classes. - /// - /// The selector object that identifies which input to switch to. - public abstract void ExecuteSwitch(object selector); - - /// - /// Links the display device to an API using a trilist, join start, join map key, and bridge. - /// This overload uses serialized join map configuration. - /// - /// The display device to link. - /// The BasicTriList for communication. - /// The starting join number for the device. - /// The key for the join map configuration. - /// The EISC API bridge instance. - protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey, - EiscApiAdvanced bridge) - { - var joinMap = new DisplayControllerJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); } - LinkDisplayToApi(displayDevice, trilist, joinMap); - } + /// + /// Powers on the display device. Must be implemented by derived classes. + /// + public abstract void PowerOn(); - /// - /// Links the display device to an API using a trilist and join map. - /// This overload uses a pre-configured join map instance. - /// - /// The display device to link. - /// The BasicTriList for communication. - /// The join map configuration for the device. - protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap) - { - this.LogDebug("Linking to Trilist {ipId}", trilist.ID.ToString("X")); - this.LogDebug("Linking to Display: {displayName}", displayDevice.Name); + /// + /// Powers off the display device. Must be implemented by derived classes. + /// + public abstract void PowerOff(); - trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name; + /// + /// Toggles the power state of the display device. Must be implemented by derived classes. + /// + public abstract void PowerToggle(); - if (displayDevice is ICommunicationMonitor commMonitor) + /// + /// Gets the collection of feedback objects for this display device. + /// + /// + public virtual FeedbackCollection Feedbacks { get; private set; } = new FeedbackCollection(); + + + /// + /// Executes a switch to the specified input on the display device. Must be implemented by derived classes. + /// + /// The selector object that identifies which input to switch to. + public abstract void ExecuteSwitch(object selector); + + /// + /// Links the display device to an API using a trilist, join start, join map key, and bridge. + /// This overload uses serialized join map configuration. + /// + /// The display device to link. + /// The BasicTriList for communication. + /// The starting join number for the device. + /// The key for the join map configuration. + /// The EISC API bridge instance. + protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey, + EiscApiAdvanced bridge) { - commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - } + var joinMap = new DisplayControllerJoinMap(joinStart); - // TODO: revisit this as there could be issues with this approach - var inputNumber = 0; - var inputKeys = new List(); + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - var inputNumberFeedback = new IntFeedback("inputNumber", () => inputNumber); + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - // Add input number feedback to the device feedback collection to keep it around... - Feedbacks.Add(inputNumberFeedback); - - // Two way feedbacks - if (displayDevice is TwoWayDisplayBase twoWayDisplay) - { - trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true); - - twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => this.LogDebug("CurrentInputFeedback_OutputChange {input}", a.StringValue); - - inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]); - - twoWayDisplay.PowerIsOnFeedback.OutputChange += (o, a) => + if (bridge != null) { - if (!a.BoolValue) - { - inputNumber = 102; - inputNumberFeedback.FireUpdate(); - - } - else - { - inputNumber = 0; - inputNumberFeedback.FireUpdate(); - } - }; - - twoWayDisplay.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); - twoWayDisplay.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); - } - - // Power Off - trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => - { - inputNumber = 102; - inputNumberFeedback.FireUpdate(); - displayDevice.PowerOff(); - }); - - // PowerOn - trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => - { - inputNumber = 0; - inputNumberFeedback.FireUpdate(); - displayDevice.PowerOn(); - }); - - - for (int i = 0; i < displayDevice.InputPorts.Count; i++) - { - var localindex = i; - if (localindex >= joinMap.InputNamesOffset.JoinSpan) - { - this.LogWarning("Device has {inputCount} inputs. The Join Map allows up to {joinSpan} inputs. Discarding inputs {discardStart} - {discardEnd} from bridge.", - displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, localindex + 1, displayDevice.InputPorts.Count); - - continue; + bridge.AddJoinMap(Key, joinMap); } else { - inputKeys.Add(displayDevice.InputPorts[localindex].Key); - - var tempKey = inputKeys.ElementAt(localindex); - - trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + localindex), () => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector)); - - this.LogDebug("Setting Input Select Action on Digital Join {joinNumber} to Input: {input}", joinMap.InputSelectOffset.JoinNumber + localindex, displayDevice.InputPorts[tempKey].Key); - - trilist.SetString((uint)(joinMap.InputNamesOffset.JoinNumber + localindex), displayDevice.InputPorts[localindex].Key); + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); } + + LinkDisplayToApi(displayDevice, trilist, joinMap); } - this.LogDebug("Setting Input Select Action on Analog Join {inputSelectJoin}", joinMap.InputSelect); - - trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (requestedInput) => + /// + /// Links the display device to an API using a trilist and join map. + /// This overload uses a pre-configured join map instance. + /// + /// The display device to link. + /// The BasicTriList for communication. + /// The join map configuration for the device. + protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap) { - if (requestedInput == 0) + this.LogDebug("Linking to Trilist {ipId}", trilist.ID.ToString("X")); + this.LogDebug("Linking to Display: {displayName}", displayDevice.Name); + + trilist.StringInput[joinMap.Name.JoinNumber].StringValue = displayDevice.Name; + + if (displayDevice is ICommunicationMonitor commMonitor) { + commMonitor.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + } + + // TODO: revisit this as there could be issues with this approach + var inputNumber = 0; + var inputKeys = new List(); + + var inputNumberFeedback = new IntFeedback("inputNumber", () => inputNumber); + + // Add input number feedback to the device feedback collection to keep it around... + Feedbacks.Add(inputNumberFeedback); + + // Two way feedbacks + if (displayDevice is TwoWayDisplayBase twoWayDisplay) + { + trilist.SetBool(joinMap.IsTwoWayDisplay.JoinNumber, true); + + twoWayDisplay.CurrentInputFeedback.OutputChange += (o, a) => this.LogDebug("CurrentInputFeedback_OutputChange {input}", a.StringValue); + + inputNumberFeedback.LinkInputSig(trilist.UShortInput[joinMap.InputSelect.JoinNumber]); + + twoWayDisplay.PowerIsOnFeedback.OutputChange += (o, a) => + { + if (!a.BoolValue) + { + inputNumber = 102; + inputNumberFeedback.FireUpdate(); + + } + else + { + inputNumber = 0; + inputNumberFeedback.FireUpdate(); + } + }; + + twoWayDisplay.PowerIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.PowerOff.JoinNumber]); + twoWayDisplay.PowerIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PowerOn.JoinNumber]); + } + + // Power Off + trilist.SetSigTrueAction(joinMap.PowerOff.JoinNumber, () => + { + inputNumber = 102; + inputNumberFeedback.FireUpdate(); displayDevice.PowerOff(); + }); + + // PowerOn + trilist.SetSigTrueAction(joinMap.PowerOn.JoinNumber, () => + { inputNumber = 0; - return; - } + inputNumberFeedback.FireUpdate(); + displayDevice.PowerOn(); + }); - // using 1-based indexing for inputs coming from SIMPL, so need to check if the input is <= the count, not < - if (requestedInput > 0 && requestedInput <= displayDevice.InputPorts.Count && requestedInput != inputNumber) + + for (int i = 0; i < displayDevice.InputPorts.Count; i++) { - displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(requestedInput - 1).Selector); + var localindex = i; + if (localindex >= joinMap.InputNamesOffset.JoinSpan) + { + this.LogWarning("Device has {inputCount} inputs. The Join Map allows up to {joinSpan} inputs. Discarding inputs {discardStart} - {discardEnd} from bridge.", + displayDevice.InputPorts.Count, joinMap.InputNamesOffset.JoinSpan, localindex + 1, displayDevice.InputPorts.Count); - inputNumber = requestedInput; + continue; + } + else + { + inputKeys.Add(displayDevice.InputPorts[localindex].Key); - return; + var tempKey = inputKeys.ElementAt(localindex); + + trilist.SetSigTrueAction((ushort)(joinMap.InputSelectOffset.JoinNumber + localindex), () => displayDevice.ExecuteSwitch(displayDevice.InputPorts[tempKey].Selector)); + + this.LogDebug("Setting Input Select Action on Digital Join {joinNumber} to Input: {input}", joinMap.InputSelectOffset.JoinNumber + localindex, displayDevice.InputPorts[tempKey].Key); + + trilist.SetString((uint)(joinMap.InputNamesOffset.JoinNumber + localindex), displayDevice.InputPorts[localindex].Key); + } } - if (requestedInput == 102) + this.LogDebug("Setting Input Select Action on Analog Join {inputSelectJoin}", joinMap.InputSelect); + + trilist.SetUShortSigAction(joinMap.InputSelect.JoinNumber, (requestedInput) => { - displayDevice.PowerToggle(); - return; - } + if (requestedInput == 0) + { + displayDevice.PowerOff(); + inputNumber = 0; + return; + } - if (displayDevice is TwoWayDisplayBase) + // using 1-based indexing for inputs coming from SIMPL, so need to check if the input is <= the count, not < + if (requestedInput > 0 && requestedInput <= displayDevice.InputPorts.Count && requestedInput != inputNumber) + { + displayDevice.ExecuteSwitch(displayDevice.InputPorts.ElementAt(requestedInput - 1).Selector); + + inputNumber = requestedInput; + + return; + } + + if (requestedInput == 102) + { + displayDevice.PowerToggle(); + return; + } + + if (displayDevice is TwoWayDisplayBase) + { + inputNumberFeedback?.FireUpdate(); + } + }); + + + var volumeDisplay = displayDevice as IBasicVolumeControls; + if (volumeDisplay == null) return; + + trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, volumeDisplay.VolumeUp); + trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, volumeDisplay.VolumeDown); + trilist.SetSigTrueAction(joinMap.VolumeMute.JoinNumber, volumeDisplay.MuteToggle); + + var volumeDisplayWithFeedback = volumeDisplay as IBasicVolumeWithFeedback; + + if (volumeDisplayWithFeedback == null) return; + trilist.SetSigTrueAction(joinMap.VolumeMuteOn.JoinNumber, volumeDisplayWithFeedback.MuteOn); + trilist.SetSigTrueAction(joinMap.VolumeMuteOff.JoinNumber, volumeDisplayWithFeedback.MuteOff); + + + trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, volumeDisplayWithFeedback.SetVolume); + volumeDisplayWithFeedback.VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMute.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); + volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + } + + /// + public virtual void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) { - inputNumberFeedback?.FireUpdate(); + var flagValue = Convert.ToInt32(type); + // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). + // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. + if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) + { + this.LogDebug("Skipping {type}", type); + continue; + } + + this.LogDebug("setting {type}", type); + + if (signalType.HasFlag(type)) + { + UpdateCurrentSources(type, sourceListKey, sourceListItem); + } } - }); + // Raise the CurrentSourcesChanged event + CurrentSourcesChanged?.Invoke(this, EventArgs.Empty); + } + private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + if (CurrentSources.ContainsKey(signalType)) + { + CurrentSources[signalType] = sourceListItem; + } + else + { + CurrentSources.Add(signalType, sourceListItem); + } - var volumeDisplay = displayDevice as IBasicVolumeControls; - if (volumeDisplay == null) return; + // Update the current source key for the specified signal type + if (CurrentSourceKeys.ContainsKey(signalType)) + { + CurrentSourceKeys[signalType] = sourceListKey; + } + else + { + CurrentSourceKeys.Add(signalType, sourceListKey); + } + } - trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, volumeDisplay.VolumeUp); - trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, volumeDisplay.VolumeDown); - trilist.SetSigTrueAction(joinMap.VolumeMute.JoinNumber, volumeDisplay.MuteToggle); - - var volumeDisplayWithFeedback = volumeDisplay as IBasicVolumeWithFeedback; - - if (volumeDisplayWithFeedback == null) return; - trilist.SetSigTrueAction(joinMap.VolumeMuteOn.JoinNumber, volumeDisplayWithFeedback.MuteOn); - trilist.SetSigTrueAction(joinMap.VolumeMuteOff.JoinNumber, volumeDisplayWithFeedback.MuteOff); - - - trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, volumeDisplayWithFeedback.SetVolume); - volumeDisplayWithFeedback.VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMute.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); - volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); } - - /// - public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) - { - var flagValue = Convert.ToInt32(type); - // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). - // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. - if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - if (!signalType.HasFlag(type)) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - this.LogDebug("setting {type}", type); - - CurrentSources.TryGetValue(type, out var previousSource); - - UpdateCurrentSources(type, previousSource, sourceDevice); - } - } - - private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice) - { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceDevice; - } - else - { - CurrentSources.Add(signalType, sourceDevice); - } - - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) - { - CurrentSourceKeys[signalType] = sourceDevice?.Key; - } - else - { - CurrentSourceKeys.Add(signalType, sourceDevice?.Key); - } - - // Raise the CurrentSourcesChanged event - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/InputInterfaces.cs b/src/PepperDash.Essentials.Devices.Common/Displays/InputInterfaces.cs new file mode 100644 index 00000000..b4314c6b --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Displays/InputInterfaces.cs @@ -0,0 +1,89 @@ +using System; + +namespace PepperDash.Essentials.Devices.Displays +{ + /// + /// Defines the contract for IInputHdmi1 + /// + [Obsolete()] + public interface IInputHdmi1 + { + /// + /// Switches to HDMI 1 input + /// + void InputHdmi1(); + } + + /// + /// Defines the contract for IInputHdmi2 + /// + [Obsolete()] + public interface IInputHdmi2 + { + /// + /// Switches to HDMI 2 input + /// + void InputHdmi2(); + } + + /// + /// Defines the contract for IInputHdmi3 + /// + [Obsolete()] + public interface IInputHdmi3 + { + /// + /// Switches to HDMI 3 input + /// + void InputHdmi3(); + } + + /// + /// Defines the contract for IInputHdmi4 + /// + [Obsolete()] + public interface IInputHdmi4 + { + /// + /// Switches to HDMI 4 input + /// + void InputHdmi4(); + } + + /// + /// Defines the contract for IInputDisplayPort1 + /// + [Obsolete()] + public interface IInputDisplayPort1 + { + /// + /// Switches to DisplayPort 1 input + /// + void InputDisplayPort1(); + } + + /// + /// Defines the contract for IInputDisplayPort2 + /// + [Obsolete()] + public interface IInputDisplayPort2 + { + /// + /// Switches to DisplayPort 2 input + /// + void InputDisplayPort2(); + } + + /// + /// Defines the contract for IInputVga1 + /// + [Obsolete()] + public interface IInputVga1 + { + /// + /// Switches to VGA 1 input + /// + void InputVga1(); + } + +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/Interfaces/IDisplayCurrentInput.cs b/src/PepperDash.Essentials.Devices.Common/Displays/Interfaces/IDisplayCurrentInput.cs deleted file mode 100644 index f2aa91f4..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Displays/Interfaces/IDisplayCurrentInput.cs +++ /dev/null @@ -1,14 +0,0 @@ -using PepperDash.Core; - -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for a display device that has current input information. This is used to provide a common interface for the TwoWayDisplayBaseMessenger to get current input information without requiring the full TwoWayDisplayBase class -/// -public interface IDisplayCurrentInput : IKeyName -{ - /// - /// Gets the Current Input feedback for the display device. - /// - StringFeedback CurrentInputFeedback { get; } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs index a7e30660..6f46b1a4 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplay.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; using PepperDash.Essentials.Core; @@ -10,76 +10,82 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.DeviceTypeInterfaces; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Displays; - -/// -/// Represents a mock display device for testing and simulation purposes. -/// -public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs, IHasPowerControlWithFeedback +namespace PepperDash.Essentials.Devices.Common.Displays { - /// - public ISelectableItems Inputs { get; private set; } - - bool _PowerIsOn; - bool _IsWarmingUp; - bool _IsCoolingDown; - - /// - protected override Func PowerIsOnFeedbackFunc - { - get - { - return () => - { - return _PowerIsOn; - }; - } - } - - /// - protected override Func IsCoolingDownFeedbackFunc - { - get - { - return () => - { - return _IsCoolingDown; - }; - } - } - - /// - protected override Func IsWarmingUpFeedbackFunc - { - get - { - return () => - { - return _IsWarmingUp; - }; - } - } - - /// - protected override Func CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } } - - int VolumeHeldRepeatInterval = 200; - ushort VolumeInterval = 655; - ushort _FakeVolumeLevel = 32768; - bool _IsMuted; - Timer _volumeUpTimer; - Timer _volumeDownTimer; - /// - /// Constructor for MockDisplay - /// - /// - public MockDisplay(string key, string name) - : base(key, name) + /// Represents a MockDisplay + /// + public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback { - Inputs = new MockDisplayInputs + /// + /// Gets or sets the Inputs + /// + public ISelectableItems Inputs { get; private set; } + + bool _PowerIsOn; + bool _IsWarmingUp; + bool _IsCoolingDown; + + /// + /// Gets the power is on feedback function + /// + protected override Func PowerIsOnFeedbackFunc { - Items = new Dictionary + get + { + return () => + { + return _PowerIsOn; + }; + } + } + /// + /// Gets the is cooling down feedback function + /// + protected override Func IsCoolingDownFeedbackFunc + { + get + { + return () => + { + return _IsCoolingDown; + }; + } + } + /// + /// Gets the is warming up feedback function + /// + protected override Func IsWarmingUpFeedbackFunc + { + get + { + return () => + { + return _IsWarmingUp; + }; + } + } + /// + /// Gets the current input feedback function + /// + protected override Func CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } } + + int VolumeHeldRepeatInterval = 200; + ushort VolumeInterval = 655; + ushort _FakeVolumeLevel = 31768; + bool _IsMuted; + + /// + /// Initializes a new instance of the MockDisplay class + /// + /// The device key + /// The device name + public MockDisplay(string key, string name) + : base(key, name) + { + Inputs = new MockDisplayInputs + { + Items = new Dictionary { { "HDMI1", new MockDisplayInput ( "HDMI1", "HDMI 1",this ) }, { "HDMI2", new MockDisplayInput ("HDMI2", "HDMI 2",this ) }, @@ -87,267 +93,276 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA { "HDMI4", new MockDisplayInput ("HDMI4", "HDMI 4",this )}, { "DP", new MockDisplayInput ("DP", "DisplayPort", this ) } } - }; + }; - Inputs.CurrentItemChanged += (o, a) => CurrentInputFeedback.FireUpdate(); + Inputs.CurrentItemChanged += (o, a) => CurrentInputFeedback.FireUpdate(); - var hdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.AudioVideo, + var hdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, "HDMI1", this); - var hdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.Hdmi, "HDMI2", this); - var hdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.Hdmi, "HDMI3", this); - var hdmiIn4 = new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.Hdmi, "HDMI4", this); - var dpIn = new RoutingInputPort(RoutingPortNames.DisplayPortIn, eRoutingSignalType.AudioVideo, - eRoutingPortConnectionType.DisplayPort, "DP", this); - InputPorts.AddRange(new[] { hdmiIn1, hdmiIn2, hdmiIn3, hdmiIn4, dpIn }); + var hdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, "HDMI2", this); + var hdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, "HDMI3", this); + var hdmiIn4 = new RoutingInputPort(RoutingPortNames.HdmiIn4, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.Hdmi, "HDMI4", this); + var dpIn = new RoutingInputPort(RoutingPortNames.DisplayPortIn, eRoutingSignalType.AudioVideo, + eRoutingPortConnectionType.DisplayPort, "DP", this); + InputPorts.AddRange(new[] { hdmiIn1, hdmiIn2, hdmiIn3, hdmiIn4, dpIn }); + VolumeLevelFeedback = new IntFeedback("volume", () => { return _FakeVolumeLevel; }); + MuteFeedback = new BoolFeedback("muteOn", () => _IsMuted); - VolumeLevelFeedback = new IntFeedback("volume", () => { return _FakeVolumeLevel; }); - MuteFeedback = new BoolFeedback("muteOn", () => _IsMuted); - - // Seed the feedback's cached value from _FakeVolumeLevel immediately. IntFeedback.IntValue only - // reflects the current backing value after FireUpdate has run once, so without this the UI would - // report 0 until the volume was first incremented/decremented. - VolumeLevelFeedback.FireUpdate(); - - WarmupTime = 10000; - CooldownTime = 10000; - } - - /// - /// PowerOn method - /// - /// - public override void PowerOn() - { - if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) - { - _IsWarmingUp = true; - IsWarmingUpFeedback.InvokeFireUpdate(); - // Fake power-up cycle - WarmupTimer = new Timer(WarmupTime) { AutoReset = false }; - WarmupTimer.Elapsed += (s, e) => - { - _IsWarmingUp = false; - _PowerIsOn = true; - IsWarmingUpFeedback.InvokeFireUpdate(); - PowerIsOnFeedback.InvokeFireUpdate(); - }; - WarmupTimer.Start(); + WarmupTime = 10000; + CooldownTime = 10000; } - } - /// - public override void PowerOff() - { - // If a display has unreliable-power off feedback, just override this and - // remove this check. - if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) + /// + /// PowerOn method + /// + /// + public override void PowerOn() { - _IsCoolingDown = true; - IsCoolingDownFeedback.InvokeFireUpdate(); - // Fake cool-down cycle - CooldownTimer = new Timer(CooldownTime) { AutoReset = false }; - CooldownTimer.Elapsed += (s, e) => - { - Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this); - _IsCoolingDown = false; - IsCoolingDownFeedback.InvokeFireUpdate(); - _PowerIsOn = false; - PowerIsOnFeedback.InvokeFireUpdate(); - }; - CooldownTimer.Start(); - } - } - - /// - public override void PowerToggle() - { - if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue) - PowerOff(); - else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue) - PowerOn(); - } - - /// - public override void ExecuteSwitch(object selector) - { - try - { - Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector); - - if (!_PowerIsOn) + if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) { + _IsWarmingUp = true; + IsWarmingUpFeedback.InvokeFireUpdate(); + // Fake power-up cycle + WarmupTimer = new CTimer(o => + { + _IsWarmingUp = false; + _PowerIsOn = true; + IsWarmingUpFeedback.InvokeFireUpdate(); + PowerIsOnFeedback.InvokeFireUpdate(); + }, WarmupTime); + } + } + + /// + /// PowerOff method + /// + /// + public override void PowerOff() + { + // If a display has unreliable-power off feedback, just override this and + // remove this check. + if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown) + { + _IsCoolingDown = true; + IsCoolingDownFeedback.InvokeFireUpdate(); + // Fake cool-down cycle + CooldownTimer = new CTimer(o => + { + Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this); + _IsCoolingDown = false; + IsCoolingDownFeedback.InvokeFireUpdate(); + _PowerIsOn = false; + PowerIsOnFeedback.InvokeFireUpdate(); + }, CooldownTime); + } + } + + /// + /// PowerToggle method + /// + /// + public override void PowerToggle() + { + if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue) + PowerOff(); + else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue) PowerOn(); + } + + /// + /// ExecuteSwitch method + /// + /// + public override void ExecuteSwitch(object selector) + { + try + { + Debug.LogMessage(LogEventLevel.Verbose, "ExecuteSwitch: {0}", this, selector); + + if (!_PowerIsOn) + { + PowerOn(); + } + + if (!Inputs.Items.TryGetValue(selector.ToString(), out var input)) + return; + + Debug.LogMessage(LogEventLevel.Verbose, "Selected input: {input}", this, input.Key); + input.Select(); + + var inputPort = InputPorts.FirstOrDefault(port => + { + Debug.LogMessage(LogEventLevel.Verbose, "Checking input port {inputPort} with selector {portSelector} against {selector}", this, port, port.Selector, selector); + return port.Selector.ToString() == selector.ToString(); + }); + + if (inputPort == null) + { + Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector); + return; + } + + Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort); + CurrentInputPort = inputPort; + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error making switch: {Exception}", this, ex); + } + } + + /// + /// SetInput method + /// + public void SetInput(string selector) + { + ISelectableItem currentInput = null; + + try + { + currentInput = Inputs.Items.SingleOrDefault(Inputs => Inputs.Value.IsSelected).Value; + } + catch { } + + + if (currentInput != null) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "SetInput: {0}", selector); + currentInput.IsSelected = false; } - if (!Inputs.Items.TryGetValue(selector.ToString(), out var input)) + if (!Inputs.Items.TryGetValue(selector, out var input)) return; - Debug.LogMessage(LogEventLevel.Verbose, "Selected input: {input}", this, input.Key); - input.Select(); + input.IsSelected = true; - var inputPort = InputPorts.FirstOrDefault(port => - { - Debug.LogMessage(LogEventLevel.Verbose, "Checking input port {inputPort} with selector {portSelector} against {selector}", this, port, port.Selector, selector); - return port.Selector.ToString() == selector.ToString(); - }); + Inputs.CurrentItem = selector; + } - if (inputPort == null) + + #region IBasicVolumeWithFeedback Members + + /// + /// Gets or sets the VolumeLevelFeedback + /// + public IntFeedback VolumeLevelFeedback { get; private set; } + + /// + /// SetVolume method + /// + public void SetVolume(ushort level) + { + _FakeVolumeLevel = level; + VolumeLevelFeedback.InvokeFireUpdate(); + } + + /// + /// MuteOn method + /// + public void MuteOn() + { + _IsMuted = true; + MuteFeedback.InvokeFireUpdate(); + } + + /// + /// MuteOff method + /// + public void MuteOff() + { + _IsMuted = false; + MuteFeedback.InvokeFireUpdate(); + } + + /// + /// Gets or sets the MuteFeedback + /// + public BoolFeedback MuteFeedback { get; private set; } + + + #endregion + + #region IBasicVolumeControls Members + + /// + /// VolumeUp method + /// + public void VolumeUp(bool pressRelease) + { + //while (pressRelease) + //{ + Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Down {0}", pressRelease); + if (pressRelease) { - Debug.LogMessage(LogEventLevel.Verbose, "Unable to find input port for selector {selector}", this, selector); - return; + var newLevel = _FakeVolumeLevel + VolumeInterval; + SetVolume((ushort)newLevel); + CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); } - - Debug.LogMessage(LogEventLevel.Verbose, "Setting current input port to {inputPort}", this, inputPort); - CurrentInputPort = inputPort; - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Error making switch: {Exception}", this, ex); - } - } - - /// - public void SetInput(string selector) - { - ISelectableItem currentInput = null; - - - if (currentInput != null) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "SetInput: {0}", selector); - currentInput.IsSelected = false; + //} } - if (!Inputs.Items.TryGetValue(selector, out var input)) - return; - - input.IsSelected = true; - - Inputs.CurrentItem = selector; - } - - - #region IBasicVolumeWithFeedback Members - - /// - public IntFeedback VolumeLevelFeedback { get; private set; } - - /// - /// SetVolume method - /// - public void SetVolume(ushort level) - { - _FakeVolumeLevel = level; - VolumeLevelFeedback.InvokeFireUpdate(); - } - - /// - /// MuteOn method - /// - public void MuteOn() - { - _IsMuted = true; - MuteFeedback.InvokeFireUpdate(); - } - - /// - /// MuteOff method - /// - public void MuteOff() - { - _IsMuted = false; - MuteFeedback.InvokeFireUpdate(); - } - - /// - /// Gets or sets the MuteFeedback - /// - public BoolFeedback MuteFeedback { get; private set; } - - - #endregion - - #region IBasicVolumeControls Members - - /// - public void VolumeUp(bool pressRelease) - { - if (pressRelease) + /// + /// VolumeDown method + /// + public void VolumeDown(bool pressRelease) { - SetVolume((ushort)(_FakeVolumeLevel + VolumeInterval)); - if (_volumeUpTimer == null) + //while (pressRelease) + //{ + Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Up {0}", pressRelease); + if (pressRelease) { - _volumeUpTimer = new Timer(VolumeHeldRepeatInterval) { AutoReset = true }; - _volumeUpTimer.Elapsed += (s, e) => SetVolume((ushort)(_FakeVolumeLevel + VolumeInterval)); - _volumeUpTimer.Start(); + var newLevel = _FakeVolumeLevel - VolumeInterval; + SetVolume((ushort)newLevel); + CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); } + //} } - else - { - _volumeUpTimer?.Stop(); - _volumeUpTimer = null; - } - } - /// - public void VolumeDown(bool pressRelease) - { - if (pressRelease) + /// + /// MuteToggle method + /// + public void MuteToggle() { - SetVolume((ushort)(_FakeVolumeLevel - VolumeInterval)); - if (_volumeDownTimer == null) - { - _volumeDownTimer = new Timer(VolumeHeldRepeatInterval) { AutoReset = true }; - _volumeDownTimer.Elapsed += (s, e) => SetVolume((ushort)(_FakeVolumeLevel - VolumeInterval)); - _volumeDownTimer.Start(); - } + _IsMuted = !_IsMuted; + MuteFeedback.InvokeFireUpdate(); } - else + + #endregion + + /// + /// LinkToApi method + /// + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) { - _volumeDownTimer?.Stop(); - _volumeDownTimer = null; + LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); } } /// - /// MuteToggle method + /// Represents a MockDisplayFactory /// - public void MuteToggle() + public class MockDisplayFactory : EssentialsDeviceFactory { - _IsMuted = !_IsMuted; - MuteFeedback.InvokeFireUpdate(); - } + /// + /// Initializes a new instance of the MockDisplayFactory class + /// + public MockDisplayFactory() + { + TypeNames = new List() { "mockdisplay", "mockdisplay2" }; + } - #endregion - - /// - public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device"); + return new MockDisplay(dc.Key, dc.Name); + } } -} - -/// -/// Represents a MockDisplayFactory -/// -public class MockDisplayFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the MockDisplayFactory class - /// - public MockDisplayFactory() - { - TypeNames = new List() { "mockdisplay", "mockdisplay2" }; - } - - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device"); - return new MockDisplay(dc.Key, dc.Name); - } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplayInputs.cs b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplayInputs.cs index 77155a67..b30963ac 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplayInputs.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/MockDisplayInputs.cs @@ -2,95 +2,135 @@ using System.Collections.Generic; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -namespace PepperDash.Essentials.Devices.Common.Displays; - -public class MockDisplayInputs : ISelectableItems +namespace PepperDash.Essentials.Devices.Common.Displays { - private Dictionary _items; - - public Dictionary Items + /// + /// Represents a MockDisplayInputs + /// + public class MockDisplayInputs : ISelectableItems { - get - { - return _items; - } - set - { - if (_items == value) - return; + private Dictionary _items; - _items = value; + /// + /// Gets or sets the collection of selectable items + /// + public Dictionary Items + { + get + { + return _items; + } + set + { + if (_items == value) + return; - ItemsUpdated?.Invoke(this, null); + _items = value; + + ItemsUpdated?.Invoke(this, null); + } } + + private string _currentItem; + + /// + /// Gets or sets the currently selected item + /// + public string CurrentItem + { + get + { + return _currentItem; + } + set + { + if (_currentItem == value) + return; + + _currentItem = value; + + CurrentItemChanged?.Invoke(this, null); + } + } + + /// + /// Occurs when the items collection is updated + /// + public event EventHandler ItemsUpdated; + /// + /// Occurs when the current item changes + /// + public event EventHandler CurrentItemChanged; } - private string _currentItem; - - public string CurrentItem - { - get - { - return _currentItem; - } - set - { - if (_currentItem == value) - return; - - _currentItem = value; - - CurrentItemChanged?.Invoke(this, null); - } - } - - public event EventHandler ItemsUpdated; - public event EventHandler CurrentItemChanged; -} - -public class MockDisplayInput : ISelectableItem -{ - private MockDisplay _parent; - - private bool _isSelected; - - public bool IsSelected + /// + /// Represents a MockDisplayInput + /// + public class MockDisplayInput : ISelectableItem { - get + private MockDisplay _parent; + + private bool _isSelected; + + /// + /// Gets or sets a value indicating whether this input is selected + /// + public bool IsSelected { - return _isSelected; + get + { + return _isSelected; + } + set + { + if (_isSelected == value) + return; + + _isSelected = value; + + ItemUpdated?.Invoke(this, null); + } } - set + + /// + /// Gets or sets the Name + /// + public string Name { get; set; } + + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + + /// + /// Occurs when this item is updated + /// + public event EventHandler ItemUpdated; + + /// + /// Initializes a new instance of the MockDisplayInput class + /// + /// The input key + /// The input name + /// The parent mock display + public MockDisplayInput(string key, string name, MockDisplay parent) { - if (_isSelected == value) - return; - - _isSelected = value; - - ItemUpdated?.Invoke(this, null); + Key = key; + Name = name; + _parent = parent; } - } - public string Name { get; set; } - - public string Key { get; set; } - - public event EventHandler ItemUpdated; - - public MockDisplayInput(string key, string name, MockDisplay parent) - { - Key = key; - Name = name; - _parent = parent; - } - - public void Select() - { - if (!_parent.PowerIsOnFeedback.BoolValue) _parent.PowerOn(); - - foreach(var input in _parent.Inputs.Items) + /// + /// Select method + /// + public void Select() { - input.Value.IsSelected = input.Key == this.Key; + if (!_parent.PowerIsOnFeedback.BoolValue) _parent.PowerOn(); + + foreach (var input in _parent.Inputs.Items) + { + input.Value.IsSelected = input.Key == this.Key; + } } } } diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftController.cs b/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftController.cs index a861d6ac..d542f52a 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftController.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftController.cs @@ -152,6 +152,15 @@ namespace PepperDash.Essentials.Devices.Common.Shades private void IsCoolingDownFeedback_OutputChange(object sender, FeedbackEventArgs e) { + if (Config.DisableAutoRaiseOnPowerOff) + { + this.LogDebug( + "Auto-raise on power-off disabled for {type}; leaving position unchanged (manual control only)", + Type + ); + return; + } + if ( !DisplayDevice.IsCoolingDownFeedback.BoolValue && Type == eScreenLiftControlType.lift @@ -174,6 +183,15 @@ namespace PepperDash.Essentials.Devices.Common.Shades { if (DisplayDevice.IsWarmingUpFeedback.BoolValue) { + if (Config.DisableAutoLowerOnPowerOn) + { + this.LogDebug( + "Auto-lower on power-on disabled for {type}; leaving position unchanged (manual control only)", + Type + ); + return; + } + Lower(); } } @@ -182,7 +200,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades /// CustomActivate method /// /// - protected override bool CustomActivate() + public override bool CustomActivate() { //Create ISwitchedOutput objects based on props switch (Mode) @@ -434,7 +452,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades /// /// Factory for ScreenLiftController devices /// - public class ScreenLiftControllerFactory : EssentialsDeviceFactory + public class ScreenLiftControllerFactory : EssentialsDeviceFactory { /// /// Constructor for ScreenLiftControllerFactory @@ -453,4 +471,4 @@ namespace PepperDash.Essentials.Devices.Common.Shades return new ScreenLiftController(dc.Key, dc.Name, props); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftControllerConfigProperties.cs b/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftControllerConfigProperties.cs index 1c4f9906..88113a08 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftControllerConfigProperties.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/ScreenLiftControllerConfigProperties.cs @@ -41,5 +41,23 @@ namespace PepperDash.Essentials.Devices.Common.Shades /// [JsonProperty("muteOnScreenUp")] public bool MuteOnScreenUp { get; set; } + + /// + /// When true, this controller does NOT automatically lower when its assigned display powers on + /// (warms up). Manual Raise/Lower still work, and the power-off auto-raise is unaffected. Intended + /// for a projector screen that must not auto-drop in a public space for safety, while the projector + /// lift (a separate controller) can still drop automatically. + /// + [JsonProperty("disableAutoLowerOnPowerOn")] + public bool DisableAutoLowerOnPowerOn { get; set; } + + /// + /// When true, this controller does NOT automatically raise when its assigned display powers off + /// (cools down). Manual Raise/Lower still work, and the power-on auto-lower is unaffected. The + /// companion to ; together they make a controller fully + /// manual while leaving other controllers (e.g. the lift) on their default automatic behavior. + /// + [JsonProperty("disableAutoRaiseOnPowerOff")] + public bool DisableAutoRaiseOnPowerOff { get; set; } } } diff --git a/src/PepperDash.Essentials.Devices.Common/Displays/TwoWayDisplayBase.cs b/src/PepperDash.Essentials.Devices.Common/Displays/TwoWayDisplayBase.cs index 92dc2007..226d4bbe 100644 --- a/src/PepperDash.Essentials.Devices.Common/Displays/TwoWayDisplayBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Displays/TwoWayDisplayBase.cs @@ -7,7 +7,7 @@ namespace PepperDash.Essentials.Devices.Common.Displays /// Abstract base class for two-way display devices that provide feedback capabilities. /// Extends DisplayBase with routing feedback and power control feedback functionality. ///
- public abstract class TwoWayDisplayBase : DisplayBase, IHasPowerControlWithFeedback + public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback { /// /// Gets feedback for the current input selection on the display. diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs index 7e1c7297..64617770 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSink.cs @@ -7,148 +7,152 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Routing; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Generic; - -/// -/// Represents a GenericSink -/// -public class GenericSink : EssentialsDevice, IRoutingSinkWithFeedback +namespace PepperDash.Essentials.Devices.Common.Generic { - /// - public Dictionary CurrentSources { get; private set; } - - /// - public Dictionary CurrentSourceKeys { get; private set; } - - /// - public event EventHandler CurrentSourcesChanged; - /// - /// Initializes a new instance of the GenericSink class + /// Represents a GenericSink /// - /// The device key - /// The device name - public GenericSink(string key, string name) : base(key, name) + public class GenericSink : EssentialsDevice, IRoutingSinkWithSwitchingWithInputPort, ICurrentSources { - InputPorts = new RoutingPortCollection(); + /// + public Dictionary CurrentSources { get; private set; } - var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); - var audioInputPort = new RoutingInputPort(RoutingPortNames.AnyAudioIn, eRoutingSignalType.Audio, eRoutingPortConnectionType.LineAudio, null, this); - - InputPorts.Add(inputPort); - InputPorts.Add(audioInputPort); + /// + public Dictionary CurrentSourceKeys { get; private set; } - CurrentSources = new Dictionary + /// + public event EventHandler CurrentSourcesChanged; + + /// + /// Initializes a new instance of the GenericSink class + /// + /// The device key + /// The device name + public GenericSink(string key, string name) : base(key, name) + { + InputPorts = new RoutingPortCollection(); + + var inputPort = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo | eRoutingSignalType.SecondaryAudio, eRoutingPortConnectionType.Hdmi, null, this); + + InputPorts.Add(inputPort); + + CurrentSources = new Dictionary { { eRoutingSignalType.Audio, null }, { eRoutingSignalType.Video, null }, }; - CurrentSourceKeys = new Dictionary + CurrentSourceKeys = new Dictionary { { eRoutingSignalType.Audio, string.Empty }, { eRoutingSignalType.Video, string.Empty }, }; + } + + /// + public void SetCurrentSource(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) + { + var flagValue = Convert.ToInt32(type); + // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). + // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. + if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) + { + this.LogDebug("Skipping {type}", type); + continue; + } + + this.LogDebug("setting {type}", type); + + if (signalType.HasFlag(type)) + { + UpdateCurrentSources(type, sourceListKey, sourceListItem); + } + } + // Raise the CurrentSourcesChanged event + CurrentSourcesChanged?.Invoke(this, EventArgs.Empty); + } + + private void UpdateCurrentSources(eRoutingSignalType signalType, string sourceListKey, SourceListItem sourceListItem) + { + CurrentSources[signalType] = sourceListItem; + CurrentSourceKeys[signalType] = sourceListKey; + } + + /// + /// Gets or sets the InputPorts + /// + public RoutingPortCollection InputPorts { get; private set; } + + /// + /// Gets or sets the CurrentSourceInfoKey + /// + public string CurrentSourceInfoKey { get; set; } + + private SourceListItem _currentSource; + /// + /// Gets or sets the CurrentSourceInfo + /// + public SourceListItem CurrentSourceInfo + { + get => _currentSource; + set + { + if (value == _currentSource) + { + return; + } + + CurrentSourceChange?.Invoke(_currentSource, ChangeType.WillChange); + + _currentSource = value; + + CurrentSourceChange?.Invoke(_currentSource, ChangeType.DidChange); + } + } + + /// + /// Gets the current input port + /// + public RoutingInputPort CurrentInputPort => InputPorts[0]; + + /// + /// Event fired when the current source changes + /// + public event SourceInfoChangeHandler CurrentSourceChange; + + /// + public event InputChangedEventHandler InputChanged; + + /// + public void ExecuteSwitch(object inputSelector) + { + this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector); + } } - /// - public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) - { - var flagValue = Convert.ToInt32(type); - // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). - // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. - if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - if (!signalType.HasFlag(type)) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - this.LogDebug("setting {type}", type); - - CurrentSources.TryGetValue(type, out var previousSource); - - UpdateCurrentSources(type, previousSource, sourceDevice); - } - } - - private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice) - { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceDevice; - } - else - { - CurrentSources.Add(signalType, sourceDevice); - } - - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) - { - CurrentSourceKeys[signalType] = sourceDevice?.Key; - } - else - { - CurrentSourceKeys.Add(signalType, sourceDevice?.Key); - } - - // Raise the CurrentSourcesChanged event - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - /// - /// Gets or sets the InputPorts + /// Represents a GenericSinkFactory /// - public RoutingPortCollection InputPorts { get; private set; } - - private SourceListItem _currentSource; - - /// - /// Gets the current input port - /// - public RoutingInputPort CurrentInputPort => InputPorts[0]; - - /// - public event InputChangedEventHandler InputChanged; - - /// - public void ExecuteSwitch(object inputSelector) + public class GenericSinkFactory : EssentialsDeviceFactory { - this.LogDebug("GenericSink Executing Switch to: {inputSelector}", inputSelector); + /// + /// Initializes a new instance of the GenericSinkFactory class + /// + public GenericSinkFactory() + { + TypeNames = new List() { "genericsink", "genericdestination" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Sink Device"); + return new GenericSink(dc.Key, dc.Name); + } } } - -/// -/// Represents a GenericSinkFactory -/// -public class GenericSinkFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the GenericSinkFactory class - /// - public GenericSinkFactory() - { - TypeNames = new List() { "genericsink", "genericdestination" }; - } - - /// - /// BuildDevice method - /// - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Sink Device"); - return new GenericSink(dc.Key, dc.Name); - } -} - - - diff --git a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs index 9926f31b..3d0f1304 100644 --- a/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs +++ b/src/PepperDash.Essentials.Devices.Common/Generic/GenericSource.cs @@ -5,76 +5,77 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common; - -/// -/// Represents a GenericSource -/// -public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking -{ - - /// - /// Gets or sets the DisplayUiType - /// - public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } } - - /// - /// Initializes a new instance of the GenericSource class - /// - /// The device key - /// The device name - public GenericSource(string key, string name) - : base(key, name) - { - - AnyOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - OutputPorts = new RoutingPortCollection { AnyOut }; - } - - #region IRoutingOutputs Members - - /// - /// Gets or sets the AnyOut - /// - public RoutingOutputPort AnyOut { get; private set; } - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - #endregion - - #region IUsageTracking Members - - /// - /// Gets or sets the UsageTracker - /// - public UsageTracking UsageTracker { get; set; } - - #endregion -} - -/// -/// Represents a GenericSourceFactory -/// -public class GenericSourceFactory : EssentialsDeviceFactory +namespace PepperDash.Essentials.Devices.Common { /// - /// Initializes a new instance of the GenericSourceFactory class + /// Represents a GenericSource /// - public GenericSourceFactory() + public class GenericSource : EssentialsDevice, IUiDisplayInfo, IRoutingSource, IUsageTracking { - TypeNames = new List() { "genericsource" }; + + /// + /// Gets or sets the DisplayUiType + /// + public uint DisplayUiType { get { return DisplayUiConstants.TypeNoControls; } } + + /// + /// Initializes a new instance of the GenericSource class + /// + /// The device key + /// The device name + public GenericSource(string key, string name) + : base(key, name) + { + + AnyOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + OutputPorts = new RoutingPortCollection { AnyOut }; + } + + #region IRoutingOutputs Members + + /// + /// Gets or sets the AnyOut + /// + public RoutingOutputPort AnyOut { get; private set; } + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + + #endregion + + #region IUsageTracking Members + + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } + + #endregion } /// - /// BuildDevice method + /// Represents a GenericSourceFactory /// - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) + public class GenericSourceFactory : EssentialsDeviceFactory { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Source Device"); - return new GenericSource(dc.Key, dc.Name); + /// + /// Initializes a new instance of the GenericSourceFactory class + /// + public GenericSourceFactory() + { + TypeNames = new List() { "genericsource" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Source Device"); + return new GenericSource(dc.Key, dc.Name); + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Lighting/LightingBase.cs b/src/PepperDash.Essentials.Devices.Common/Lighting/LightingBase.cs index a77b936b..9dd3b816 100644 --- a/src/PepperDash.Essentials.Devices.Common/Lighting/LightingBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Lighting/LightingBase.cs @@ -1,4 +1,6 @@ -using System; + + +using System; using System.Collections.Generic; using System.Linq; using Crestron.SimplSharpPro.DeviceSupport; @@ -9,167 +11,167 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Lighting; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Lighting; - - -/// -/// Base class for lighting devices that support scenes -/// -public abstract class LightingBase : EssentialsBridgeableDevice, ILightingScenes +namespace PepperDash.Essentials.Devices.Common.Lighting { - #region ILightingScenes Members - /// - /// Event fired when lighting scene changes + /// Base class for lighting devices that support scenes /// - public event EventHandler LightingSceneChange; - - /// - /// Gets or sets the LightingScenes - /// - public List LightingScenes { get; protected set; } - - /// - /// Gets or sets the CurrentLightingScene - /// - public LightingScene CurrentLightingScene { get; protected set; } - - /// - /// Gets or sets the CurrentLightingSceneFeedback - /// - public IntFeedback CurrentLightingSceneFeedback { get; protected set; } - - #endregion - - /// - /// Initializes a new instance of the LightingBase class - /// - /// The device key - /// The device name - protected LightingBase(string key, string name) - : base(key, name) + public abstract class LightingBase : EssentialsBridgeableDevice, ILightingScenes { - LightingScenes = new List(); + #region ILightingScenes Members - CurrentLightingScene = new LightingScene(); - //CurrentLightingSceneFeedback = new IntFeedback(() => { return int.Parse(this.CurrentLightingScene.ID); }); - } + /// + /// Event fired when lighting scene changes + /// + public event EventHandler LightingSceneChange; - /// - /// Selects the specified lighting scene - /// - /// The lighting scene to select - public abstract void SelectScene(LightingScene scene); + /// + /// Gets or sets the LightingScenes + /// + public List LightingScenes { get; protected set; } - /// - /// SimulateSceneSelect method - /// - public void SimulateSceneSelect(string sceneName) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Simulating selection of scene '{0}'", sceneName); + /// + /// Gets or sets the CurrentLightingScene + /// + public LightingScene CurrentLightingScene { get; protected set; } - var scene = LightingScenes.FirstOrDefault(s => s.Name.Equals(sceneName)); + /// + /// Gets or sets the CurrentLightingSceneFeedback + /// + public IntFeedback CurrentLightingSceneFeedback { get; protected set; } - if (scene != null) + #endregion + + /// + /// Initializes a new instance of the LightingBase class + /// + /// The device key + /// The device name + protected LightingBase(string key, string name) + : base(key, name) { - CurrentLightingScene = scene; - OnLightingSceneChange(); + LightingScenes = new List(); + + CurrentLightingScene = new LightingScene(); + //CurrentLightingSceneFeedback = new IntFeedback(() => { return int.Parse(this.CurrentLightingScene.ID); }); } - } - /// - /// Sets the IsActive property on each scene and fires the LightingSceneChange event - /// - protected void OnLightingSceneChange() - { - foreach (var scene in LightingScenes) + /// + /// Selects the specified lighting scene + /// + /// The lighting scene to select + public abstract void SelectScene(LightingScene scene); + + /// + /// SimulateSceneSelect method + /// + public void SimulateSceneSelect(string sceneName) { - if (scene == CurrentLightingScene) - scene.IsActive = true; + Debug.LogMessage(LogEventLevel.Debug, this, "Simulating selection of scene '{0}'", sceneName); + var scene = LightingScenes.FirstOrDefault(s => s.Name.Equals(sceneName)); + + if (scene != null) + { + CurrentLightingScene = scene; + OnLightingSceneChange(); + } + } + + /// + /// Sets the IsActive property on each scene and fires the LightingSceneChange event + /// + protected void OnLightingSceneChange() + { + foreach (var scene in LightingScenes) + { + if (scene == CurrentLightingScene) + scene.IsActive = true; + + else + scene.IsActive = false; + } + LightingSceneChange?.Invoke(this, new LightingSceneChangeEventArgs(CurrentLightingScene)); + } + + /// + /// Links the lighting device to API with join map configuration + /// + /// The lighting device to link + /// The trilist to link to + /// The starting join number + /// The join map key + /// The EISC API bridge + /// The configured join map + protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart, + string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new GenericLightingJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } else - scene.IsActive = false; - } - LightingSceneChange?.Invoke(this, new LightingSceneChangeEventArgs(CurrentLightingScene)); - } + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } - /// - /// Links the lighting device to API with join map configuration - /// - /// The lighting device to link - /// The trilist to link to - /// The starting join number - /// The join map key - /// The EISC API bridge - /// The configured join map - protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, uint joinStart, - string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new GenericLightingJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + return LinkLightingToApi(lightingDevice, trilist, joinMap); } - return LinkLightingToApi(lightingDevice, trilist, joinMap); - } - - /// - /// Links the lighting device to API using an existing join map - /// - /// The lighting device to link - /// The trilist to link to - /// The join map to use - /// The join map used for linking - protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, GenericLightingJoinMap joinMap) - { - Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - - Debug.LogMessage(LogEventLevel.Information, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString()); - - // GenericLighitng Actions & FeedBack - trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); - - var sceneIndex = 0; - foreach (var scene in lightingDevice.LightingScenes) + /// + /// Links the lighting device to API using an existing join map + /// + /// The lighting device to link + /// The trilist to link to + /// The join map to use + /// The join map used for linking + protected GenericLightingJoinMap LinkLightingToApi(LightingBase lightingDevice, BasicTriList trilist, GenericLightingJoinMap joinMap) { - var index = sceneIndex; + Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + index), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[index])); - scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)]); - trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; - trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; + Debug.LogMessage(LogEventLevel.Information, "Linking to Lighting Type {0}", lightingDevice.GetType().Name.ToString()); - sceneIndex++; - } + // GenericLighitng Actions & FeedBack + trilist.SetUShortSigAction(joinMap.SelectScene.JoinNumber, u => lightingDevice.SelectScene(lightingDevice.LightingScenes[u])); - trilist.OnlineStatusChange += (sender, args) => - { - if (!args.DeviceOnLine) return; - - sceneIndex = 0; + var sceneIndex = 0; foreach (var scene in lightingDevice.LightingScenes) { var index = sceneIndex; + trilist.SetSigTrueAction((uint)(joinMap.SelectSceneDirect.JoinNumber + index), () => lightingDevice.SelectScene(lightingDevice.LightingScenes[index])); + scene.IsActiveFeedback.LinkInputSig(trilist.BooleanInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)]); trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; - scene.IsActiveFeedback.FireUpdate(); sceneIndex++; } - }; - return joinMap; + trilist.OnlineStatusChange += (sender, args) => + { + if (!args.DeviceOnLine) return; + + sceneIndex = 0; + foreach (var scene in lightingDevice.LightingScenes) + { + var index = sceneIndex; + + trilist.StringInput[(uint)(joinMap.SelectSceneDirect.JoinNumber + index)].StringValue = scene.Name; + trilist.BooleanInput[(uint)(joinMap.ButtonVisibility.JoinNumber + index)].BoolValue = true; + scene.IsActiveFeedback.FireUpdate(); + + sceneIndex++; + } + }; + + return joinMap; + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj index 76b77f1b..317fa2d3 100644 --- a/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj +++ b/src/PepperDash.Essentials.Devices.Common/PepperDash.Essentials.Devices.Common.csproj @@ -1,9 +1,9 @@  - Debug;Release; + Debug;Release;Debug 4.7.2 - net8 + net472 true bin\$(Configuration)\ Essentials Devices Common @@ -29,7 +29,6 @@ - - + \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Recording/ICourtSession.cs b/src/PepperDash.Essentials.Devices.Common/Recording/ICourtSession.cs deleted file mode 100644 index 34411a21..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Recording/ICourtSession.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.Generic; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.Devices.Common.Recording -{ - /// - /// Defines the contract for court session management including - /// session lifecycle, recording control, case management, and status feedback. - /// - public interface ICourtSession - { - #region Session Lifecycle - - /// - /// Create a new court session with the given title. - /// - bool CreateCourtSession(string title); - - /// - /// Get the active court session payload and cache it locally. - /// - bool GetActiveCourtSession(); - - /// - /// Check whether an active court session exists. - /// - bool HasActiveCourtSession(); - - /// - /// Edit the active court session title. - /// - bool EditCourtSessionTitle(string title); - - /// - /// Close the active court session. - /// - bool CloseCourtSession(); - - /// - /// Force close the active court session. - /// - bool ForceCloseCourtSession(); - - #endregion - - #region Recording Control - - /// - /// Start recording the active court session. - /// - bool StartRecording(); - - /// - /// Stop the current recording. - /// - bool StopRecording(); - - /// - /// Seal the current recording (protect/read-only). - /// - bool Seal(); - - /// - /// Unseal the current recording. - /// - bool Unseal(); - - /// - /// Get the current recording status from the API. - /// - bool GetRecordingStatus(); - - /// - /// Get the current seal status from the API. - /// - bool GetSealStatus(); - - #endregion - - - - - #region Status - - /// - /// Get current session information (case ID and name). - /// - Dictionary GetCurrentSessionInfo(); - - - /// - /// Get the activation status from the API. - /// - bool GetActivationStatus(); - - #endregion - - #region Feedbacks - - /// - /// True when currently recording. - /// - BoolFeedback IsRecording { get; } - - /// - /// True when the recording is sealed. - /// - BoolFeedback IsSealed { get; } - - /// - /// Recording duration in milliseconds. - /// - StringFeedback RecordingDurationMs { get; } - - /// - /// Recording duration formatted as HH:MM:SS. - /// - StringFeedback RecordingDurationFormatted { get; } - - /// - /// Current case ID for the active session. - /// - StringFeedback CurrentCaseId { get; } - - /// - /// Current case name for the active session. - /// - StringFeedback CurrentCaseName { get; } - - /// - /// Current recording session state (Idle, Recording, Sealed, Disconnected, Error). - /// - StringFeedback CurrentState { get; } - - /// - /// Error message if session is in error state. - /// - StringFeedback ErrorMessage { get; } - - /// - /// API heartbeat counter for diagnostics. - /// - IntFeedback HeartbeatCounter { get; } - - /// - /// True when CourtCapture reports an active court session. - /// - BoolFeedback HasActiveCourtSessionFeedback { get; } - - /// - /// Last command result envelope as JSON string for UI binding. - /// - StringFeedback LastCommandResult { get; } - - #endregion - - #region Events - - /// - /// Raised when case notes are updated. - /// - event EventHandler CaseNotesUpdated; - - /// - /// Raised when quick links are updated. - /// - event EventHandler QuickLinksUpdated; - - #endregion - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleSpaceRoom.cs b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleSpaceRoom.cs new file mode 100644 index 00000000..ecce473e --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleSpaceRoom.cs @@ -0,0 +1,28 @@ +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Room.Config; + +namespace PepperDash.Essentials.Devices.Common.Room +{ + /// + /// Defines the contract for IEssentialsHuddleSpaceRoom + /// + public interface IEssentialsHuddleSpaceRoom : IEssentialsRoom, IHasCurrentSourceInfoChange, IRunRouteAction, IHasDefaultDisplay, IHasCurrentVolumeControls, IRoomOccupancy, + IEmergency, IMicrophonePrivacy + { + /// + /// Gets whether to exclude this room from global functions + /// + bool ExcludeFromGlobalFunctions { get; } + + /// + /// Runs the route action for the given routeKey and sourceListKey + /// + /// The route key + void RunRouteAction(string routeKey); + + /// + /// Gets the PropertiesConfig + /// + EssentialsHuddleRoomPropertiesConfig PropertiesConfig { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleVtc1Room.cs b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleVtc1Room.cs new file mode 100644 index 00000000..5547f595 --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsHuddleVtc1Room.cs @@ -0,0 +1,51 @@ +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Devices.Common.AudioCodec; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Room.Config; + +namespace PepperDash.Essentials.Devices.Common.Room +{ + /// + /// Defines the contract for IEssentialsHuddleVtc1Room + /// + public interface IEssentialsHuddleVtc1Room : IEssentialsRoom, IHasCurrentSourceInfoChange, IHasCurrentVolumeControls, IRunRouteAction, IRunDefaultCallRoute, IHasVideoCodec, IHasAudioCodec, IHasDefaultDisplay, IHasInCallFeedback, + IRoomOccupancy, IEmergency, IMicrophonePrivacy + { + /// + /// Gets the PropertiesConfig + /// + EssentialsHuddleVtc1PropertiesConfig PropertiesConfig { get; } + + /// + /// Gets whether to exclude this room from global functions + /// + bool ExcludeFromGlobalFunctions { get; } + + /// + /// Runs the route action for the given routeKey and sourceListKey + /// + /// The route key + void RunRouteAction(string routeKey); + + /// + /// Gets the ScheduleSource + /// + IHasScheduleAwareness ScheduleSource { get; } + + /// + /// Gets the InCallFeedback + /// + new BoolFeedback InCallFeedback { get; } + + /// + /// Gets the PrivacyModeIsOnFeedback + /// + new BoolFeedback PrivacyModeIsOnFeedback { get; } + + /// + /// Gets the DefaultCodecRouteString + /// + string DefaultCodecRouteString { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsRoomPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsRoomPropertiesConfig.cs index 7b6db77e..e45be6e0 100644 --- a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsRoomPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsRoomPropertiesConfig.cs @@ -1,16 +1,15 @@ using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Devices.Common.Room; - - -/// -/// Defines the contract for IEssentialsRoomPropertiesConfig -/// -public interface IEssentialsRoomPropertiesConfig +namespace PepperDash.Essentials.Devices.Common.Room { /// - /// Gets the PropertiesConfig + /// Defines the contract for IEssentialsRoomPropertiesConfig /// - EssentialsRoomPropertiesConfig PropertiesConfig { get; } + public interface IEssentialsRoomPropertiesConfig + { + /// + /// Gets the PropertiesConfig + /// + EssentialsRoomPropertiesConfig PropertiesConfig { get; } + } } - diff --git a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsTechRoom.cs b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsTechRoom.cs index 2dd79a99..9301f37d 100644 --- a/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsTechRoom.cs +++ b/src/PepperDash.Essentials.Devices.Common/Room/IEssentialsTechRoom.cs @@ -5,36 +5,37 @@ using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Devices.Common.Displays; using PepperDash.Essentials.Room.Config; -namespace PepperDash.Essentials.Devices.Common.Room; -/// -/// Defines the contract for IEssentialsTechRoom -/// -public interface IEssentialsTechRoom : IEssentialsRoom, ITvPresetsProvider, IBridgeAdvanced, IRunDirectRouteAction +namespace PepperDash.Essentials.Devices.Common.Room { /// - /// Gets the PropertiesConfig + /// Defines the contract for IEssentialsTechRoom /// - EssentialsTechRoomConfig PropertiesConfig { get; } + public interface IEssentialsTechRoom : IEssentialsRoom, ITvPresetsProvider, IBridgeAdvanced, IRunDirectRouteAction + { + /// + /// Gets the PropertiesConfig + /// + EssentialsTechRoomConfig PropertiesConfig { get; } - /// - /// Gets the Tuners - /// - Dictionary Tuners { get; } + /// + /// Gets the Tuners + /// + Dictionary Tuners { get; } - /// - /// Gets the Displays - /// - Dictionary Displays { get; } + /// + /// Gets the Displays + /// + Dictionary Displays { get; } - /// - /// Powers on the room - /// - void RoomPowerOn(); + /// + /// Powers on the room + /// + void RoomPowerOn(); - /// - /// Powers off the room - /// - void RoomPowerOff(); + /// + /// Powers off the room + /// + void RoomPowerOff(); + } } - diff --git a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs deleted file mode 100644 index 74853223..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpoint.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Config; -using Serilog.Events; - -namespace PepperDash.Essentials.Devices.Common.Routing; - -/// -/// A mock midpoint routing device (e.g. a matrix switcher) that implements -/// without any real hardware communication. Its input and output ports are configured via -/// , each with a name, signal type, and physical port -/// (connection) type - so it can stand in for a real switching device (such as a StreamSync matrix) for -/// development and testing of routing logic. -/// -[Description("A mock routing midpoint (e.g. matrix switcher) device for testing routing logic without real hardware")] -public class MockRoutingMidpoint : EssentialsDevice, IRoutingMidpointWithFeedback -{ - /// - /// The configuration properties for this device. - /// - public MockRoutingMidpointPropertiesConfig PropertiesConfig { get; private set; } - - /// - public RoutingPortCollection InputPorts { get; private set; } - - /// - public RoutingPortCollection OutputPorts { get; private set; } - - /// - public List CurrentRoutes { get; } = new List(); - - /// - public event RouteChangedEventHandler RouteChanged; - - /// - /// Initializes a new instance of the class from a . - /// - /// The device configuration, whose Properties are deserialized as . - public MockRoutingMidpoint(DeviceConfig config) - : base(config.Key, config.Name) - { - PropertiesConfig = config.Properties != null - ? JsonConvert.DeserializeObject(config.Properties.ToString()) - : null; - PropertiesConfig ??= new MockRoutingMidpointPropertiesConfig(); - - InputPorts = new RoutingPortCollection(); - OutputPorts = new RoutingPortCollection(); - - BuildPorts(); - } - - /// - /// Builds the input and output ports from . Each port's Key and Selector - /// are both set to the configured port name, so selectors passed to and - /// can simply be looked up by matching against the port's Selector. - /// - private void BuildPorts() - { - try - { - foreach (var portConfig in PropertiesConfig.InputPorts) - { - if (string.IsNullOrEmpty(portConfig.Name)) - { - this.LogWarning("Skipping input port with no name configured for {key}", Key); - continue; - } - - var port = new RoutingInputPort(portConfig.Name, portConfig.SignalType, portConfig.PortType, portConfig.Name, this); - InputPorts.Add(port); - } - - foreach (var portConfig in PropertiesConfig.OutputPorts) - { - if (string.IsNullOrEmpty(portConfig.Name)) - { - this.LogWarning("Skipping output port with no name configured for {key}", Key); - continue; - } - - var port = new RoutingOutputPort(portConfig.Name, portConfig.SignalType, portConfig.PortType, portConfig.Name, this); - OutputPorts.Add(port); - } - - this.LogInformation("Built {inputCount} input port(s) and {outputCount} output port(s) for mock midpoint {key}", - InputPorts.Count, OutputPorts.Count, Key); - } - catch (Exception ex) - { - this.LogException(ex, "Error building ports for mock midpoint {0}", Key); - } - } - - /// - public void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType) - { - try - { - var outputPort = OutputPorts.FirstOrDefault(p => Equals(p.Selector, outputSelector)); - - if (outputPort == null) - { - this.LogWarning("Unable to find output port for selector {selector} on {key}", outputSelector, Key); - return; - } - - // Remove any existing route to this output before making or clearing the new one. - var existingRoute = CurrentRoutes.FirstOrDefault(r => r.OutputPort?.Key == outputPort.Key); - if (existingRoute != null) - { - CurrentRoutes.Remove(existingRoute); - } - - if (inputSelector == null) - { - this.LogInformation("Clearing route to output {output} on {key}", outputPort.Key, Key); - - var clearedDescriptor = new RouteSwitchDescriptor(outputPort, null); - RouteChanged?.Invoke(this, clearedDescriptor); - return; - } - - var inputPort = InputPorts.FirstOrDefault(p => Equals(p.Selector, inputSelector)); - - if (inputPort == null) - { - this.LogWarning("Unable to find input port for selector {selector} on {key}", inputSelector, Key); - return; - } - - var descriptor = new RouteSwitchDescriptor(outputPort, inputPort); - CurrentRoutes.Add(descriptor); - - this.LogInformation("Executed switch: {input} -> {output} ({signalType}) on {key}", - inputPort.Key, outputPort.Key, signalType, Key); - - RouteChanged?.Invoke(this, descriptor); - } - catch (Exception ex) - { - this.LogException(ex, "Error executing switch on mock midpoint {0}", Key); - } - } - - /// - public void ClearRoute(object outputSelector, eRoutingSignalType signalType) - { - ExecuteSwitch(null, outputSelector, signalType); - } -} - -/// -/// Factory for building devices. -/// -public class MockRoutingMidpointFactory : EssentialsDeviceFactory -{ - /// - /// Initializes a new instance of the class. - /// - public MockRoutingMidpointFactory() - { - TypeNames = new List { "mockroutingmidpoint", "mockmidpoint" }; - } - - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MockRoutingMidpoint Device"); - return new MockRoutingMidpoint(dc); - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs deleted file mode 100644 index 7c7d4bc3..00000000 --- a/src/PepperDash.Essentials.Devices.Common/Routing/MockRoutingMidpointPropertiesConfig.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.Devices.Common.Routing; - -/// -/// Configuration properties for a device. -/// -public class MockRoutingMidpointPropertiesConfig -{ - /// - /// The input ports to build on the mock midpoint. - /// - [JsonProperty("inputPorts")] - public List InputPorts { get; set; } = new List(); - - /// - /// The output ports to build on the mock midpoint. - /// - [JsonProperty("outputPorts")] - public List OutputPorts { get; set; } = new List(); -} - -/// -/// Configuration for a single input or output port on a device. -/// -public class MockRoutingMidpointPortConfig -{ - /// - /// The name of the port. Used as both the port's key and its selector value, so it must be unique - /// within the collection (input ports or output ports) it is configured under. - /// - [JsonProperty("name")] - public string Name { get; set; } - - /// - /// The routing signal type supported by this port (e.g. "Audio", "Video", "AudioVideo", "Usb"). - /// Defaults to AudioVideo if not specified. - /// - [JsonProperty("signalType")] - [JsonConverter(typeof(StringEnumConverter))] - public eRoutingSignalType SignalType { get; set; } = eRoutingSignalType.AudioVideo; - - /// - /// The physical connection type of this port (e.g. "Hdmi", "Dm", "DisplayPort"). Defaults to Hdmi if - /// not specified. - /// - [JsonProperty("portType")] - [JsonConverter(typeof(StringEnumConverter))] - public eRoutingPortConnectionType PortType { get; set; } = eRoutingPortConnectionType.Hdmi; -} diff --git a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs index 6171ef86..7d49b9b7 100644 --- a/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/SetTopBox/IRSetTopBoxBase.cs @@ -656,5 +656,5 @@ namespace PepperDash.Essentials.Devices.Common } } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/SetTopBox/SetTopBoxPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/SetTopBox/SetTopBoxPropertiesConfig.cs index 94c5dd51..193b41e7 100644 --- a/src/PepperDash.Essentials.Devices.Common/SetTopBox/SetTopBoxPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/SetTopBox/SetTopBoxPropertiesConfig.cs @@ -1,35 +1,36 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Devices.Common; - -/// -/// Represents a SetTopBoxPropertiesConfig -/// -public class SetTopBoxPropertiesConfig : PepperDash.Essentials.Core.Config.SourceDevicePropertiesConfigBase +namespace PepperDash.Essentials.Devices.Common { /// - /// Gets or sets the HasPresets + /// Represents a SetTopBoxPropertiesConfig /// - public bool HasPresets { get; set; } - /// - /// Gets or sets the HasDvr - /// - public bool HasDvr { get; set; } - /// - /// Gets or sets the HasDpad - /// - public bool HasDpad { get; set; } - /// - /// Gets or sets the HasNumeric - /// - public bool HasNumeric { get; set; } - /// - /// Gets or sets the IrPulseTime - /// - public int IrPulseTime { get; set; } + public class SetTopBoxPropertiesConfig : PepperDash.Essentials.Core.Config.SourceDevicePropertiesConfigBase + { + /// + /// Gets or sets the HasPresets + /// + public bool HasPresets { get; set; } + /// + /// Gets or sets the HasDvr + /// + public bool HasDvr { get; set; } + /// + /// Gets or sets the HasDpad + /// + public bool HasDpad { get; set; } + /// + /// Gets or sets the HasNumeric + /// + public bool HasNumeric { get; set; } + /// + /// Gets or sets the IrPulseTime + /// + public int IrPulseTime { get; set; } - /// - /// Gets or sets the Control - /// - public ControlPropertiesConfig Control { get; set; } -} + /// + /// Gets or sets the Control + /// + public ControlPropertiesConfig Control { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Shades/RelayControlledShade.cs b/src/PepperDash.Essentials.Devices.Common/Shades/RelayControlledShade.cs index 9e37cd8b..e0080627 100644 --- a/src/PepperDash.Essentials.Devices.Common/Shades/RelayControlledShade.cs +++ b/src/PepperDash.Essentials.Devices.Common/Shades/RelayControlledShade.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Timers; +using Crestron.SimplSharp; using PepperDash.Core; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; @@ -7,171 +7,181 @@ using PepperDash.Essentials.Core.CrestronIO; using PepperDash.Essentials.Core.Shades; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Shades; - -/// -/// Controls a single shade using three relays -/// -public class RelayControlledShade : ShadeBase, IShadesOpenCloseStop +namespace PepperDash.Essentials.Devices.Common.Shades { - RelayControlledShadeConfigProperties Config; - - ISwitchedOutput OpenRelay; - ISwitchedOutput StopOrPresetRelay; - ISwitchedOutput CloseRelay; - - int RelayPulseTime; - /// - /// Gets the label for the stop or preset button, depending on how the shade is configured + /// Controls a single shade using three relays /// - public string StopOrPresetButtonLabel { get; set; } - - /// - /// Constructor for RelayControlledShade - /// - /// - /// - /// - public RelayControlledShade(string key, string name, RelayControlledShadeConfigProperties config) - : base(key, name) + public class RelayControlledShade : ShadeBase, IShadesOpenCloseStop { - Config = config; + RelayControlledShadeConfigProperties Config; - RelayPulseTime = Config.RelayPulseTime; + ISwitchedOutput OpenRelay; + ISwitchedOutput StopOrPresetRelay; + ISwitchedOutput CloseRelay; - StopOrPresetButtonLabel = Config.StopOrPresetLabel; + int RelayPulseTime; - } + /// + /// Gets or sets the StopOrPresetButtonLabel + /// + public string StopOrPresetButtonLabel { get; set; } - /// - protected override bool CustomActivate() - { - //Create ISwitchedOutput objects based on props - OpenRelay = GetSwitchedOutputFromDevice(Config.Relays.Open); - StopOrPresetRelay = GetSwitchedOutputFromDevice(Config.Relays.StopOrPreset); - CloseRelay = GetSwitchedOutputFromDevice(Config.Relays.Close); - - - return base.CustomActivate(); - } - - /// - public override void Open() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Opening Shade: '{0}'", this.Name); - - PulseOutput(OpenRelay, RelayPulseTime); - } - - /// - - public override void Stop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Shade: '{0}'", this.Name); - - PulseOutput(StopOrPresetRelay, RelayPulseTime); - } - - /// - public override void Close() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Closing Shade: '{0}'", this.Name); - - PulseOutput(CloseRelay, RelayPulseTime); - } - - void PulseOutput(ISwitchedOutput output, int pulseTime) - { - output.On(); - var pulseTimer = new Timer(pulseTime) { AutoReset = false }; - pulseTimer.Elapsed += (s, e) => output.Off(); - pulseTimer.Start(); - } - - /// - /// Attempts to get the port on the specified device from config - /// - /// - /// - ISwitchedOutput GetSwitchedOutputFromDevice(IOPortConfig relayConfig) - { - var portDevice = DeviceManager.GetDeviceForKey(relayConfig.PortDeviceKey); - - if (portDevice != null) + /// + /// Initializes a new instance of the RelayControlledShade class + /// + /// The device key + /// The device name + /// The relay controlled shade configuration + public RelayControlledShade(string key, string name, RelayControlledShadeConfigProperties config) + : base(key, name) { - return (portDevice as ISwitchedOutputCollection).SwitchedOutputs[relayConfig.PortNumber]; + Config = config; + + RelayPulseTime = Config.RelayPulseTime; + + StopOrPresetButtonLabel = Config.StopOrPresetLabel; + } - else + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() { - Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay on port '{0}' from device with key '{1}'", relayConfig.PortNumber, relayConfig.PortDeviceKey); - return null; + //Create ISwitchedOutput objects based on props + OpenRelay = GetSwitchedOutputFromDevice(Config.Relays.Open); + StopOrPresetRelay = GetSwitchedOutputFromDevice(Config.Relays.StopOrPreset); + CloseRelay = GetSwitchedOutputFromDevice(Config.Relays.Close); + + + return base.CustomActivate(); + } + + /// + /// Open method + /// + /// + public override void Open() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Opening Shade: '{0}'", this.Name); + + PulseOutput(OpenRelay, RelayPulseTime); + } + + /// + /// Stop method + /// + /// + public override void Stop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Shade: '{0}'", this.Name); + + PulseOutput(StopOrPresetRelay, RelayPulseTime); + } + + /// + /// Close method + /// + /// + public override void Close() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Closing Shade: '{0}'", this.Name); + + PulseOutput(CloseRelay, RelayPulseTime); + } + + void PulseOutput(ISwitchedOutput output, int pulseTime) + { + output.On(); + CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime); + } + + /// + /// Attempts to get the port on teh specified device from config + /// + /// + /// + ISwitchedOutput GetSwitchedOutputFromDevice(IOPortConfig relayConfig) + { + var portDevice = DeviceManager.GetDeviceForKey(relayConfig.PortDeviceKey); + + if (portDevice != null) + { + return (portDevice as ISwitchedOutputCollection).SwitchedOutputs[relayConfig.PortNumber]; + } + else + { + Debug.LogMessage(LogEventLevel.Debug, this, "Error: Unable to get relay on port '{0}' from device with key '{1}'", relayConfig.PortNumber, relayConfig.PortDeviceKey); + return null; + } + } + + } + + /// + /// Represents a RelayControlledShadeConfigProperties + /// + public class RelayControlledShadeConfigProperties + { + /// + /// Gets or sets the RelayPulseTime + /// + public int RelayPulseTime { get; set; } + /// + /// Gets or sets the Relays + /// + public ShadeRelaysConfig Relays { get; set; } + /// + /// Gets or sets the StopOrPresetLabel + /// + public string StopOrPresetLabel { get; set; } + + /// + /// Represents a ShadeRelaysConfig + /// + public class ShadeRelaysConfig + { + /// + /// Gets or sets the Open + /// + public IOPortConfig Open { get; set; } + /// + /// Gets or sets the StopOrPreset + /// + public IOPortConfig StopOrPreset { get; set; } + /// + /// Gets or sets the Close + /// + public IOPortConfig Close { get; set; } } } -} - -/// -/// Configuration properties for RelayControlledShade -/// -public class RelayControlledShadeConfigProperties -{ /// - /// The amount of time in milliseconds to pulse the relay for when opening or closing the shade + /// Represents a RelayControlledShadeFactory /// - public int RelayPulseTime { get; set; } - - /// - /// The relays that control the shade - /// - public ShadeRelaysConfig Relays { get; set; } - - /// - /// The label for the stop or preset button, depending on how the shade is configured - /// - public string StopOrPresetLabel { get; set; } - - /// - /// Configuration for the relays that control the shade - /// - public class ShadeRelaysConfig + public class RelayControlledShadeFactory : EssentialsDeviceFactory { /// - /// The relay that opens the shade + /// Initializes a new instance of the RelayControlledShadeFactory class /// - public IOPortConfig Open { get; set; } + public RelayControlledShadeFactory() + { + TypeNames = new List() { "relaycontrolledshade" }; + } /// - /// The relay that stops the shade or presets the shade to a certain position, depending on how the shade is configured + /// BuildDevice method /// - public IOPortConfig StopOrPreset { get; set; } + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - /// - /// The relay that closes the shade - /// - public IOPortConfig Close { get; set; } - } -} - -/// -/// Factory for creating RelayControlledShade devices -/// -public class RelayControlledShadeFactory : EssentialsDeviceFactory -{ - /// - /// Constructor for RelayControlledShadeFactory - /// - public RelayControlledShadeFactory() - { - TypeNames = new List() { "relaycontrolledshade" }; + return new RelayControlledShade(dc.Key, dc.Name, props); + } } - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Generic Comm Device"); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - - return new RelayControlledShade(dc.Key, dc.Name, props); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Shades/ShadeBase.cs b/src/PepperDash.Essentials.Devices.Common/Shades/ShadeBase.cs index 6b8e13b8..ba3d67dd 100644 --- a/src/PepperDash.Essentials.Devices.Common/Shades/ShadeBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/Shades/ShadeBase.cs @@ -1,40 +1,39 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Shades; -namespace PepperDash.Essentials.Devices.Common.Shades; - - -/// -/// Base class for shade devices -/// -public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop +namespace PepperDash.Essentials.Devices.Common.Shades { /// - /// Initializes a new instance of the ShadeBase class + /// Base class for shade devices /// - /// The device key - /// The device name - public ShadeBase(string key, string name) - : base(key, name) + public abstract class ShadeBase : EssentialsDevice, IShadesOpenCloseStop { + /// + /// Initializes a new instance of the ShadeBase class + /// + /// The device key + /// The device name + public ShadeBase(string key, string name) + : base(key, name) + { + } + + #region iShadesOpenClose Members + + /// + /// Opens the shade + /// + public abstract void Open(); + /// + /// Stops the shade + /// + public abstract void Stop(); + /// + /// Closes the shade + /// + public abstract void Close(); + + #endregion } - - #region iShadesOpenClose Members - - /// - /// Opens the shade - /// - public abstract void Open(); - /// - /// Stops the shade - /// - public abstract void Stop(); - /// - /// Closes the shade - /// - public abstract void Close(); - - #endregion } - diff --git a/src/PepperDash.Essentials.Devices.Common/Shades/ShadeController.cs b/src/PepperDash.Essentials.Devices.Common/Shades/ShadeController.cs index 8ccf16cb..dc8cb125 100644 --- a/src/PepperDash.Essentials.Devices.Common/Shades/ShadeController.cs +++ b/src/PepperDash.Essentials.Devices.Common/Shades/ShadeController.cs @@ -5,96 +5,105 @@ using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Shades; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Shades; - -/// -/// Class that contains the shades to be controlled in a room -/// -public class ShadeController : EssentialsDevice, IShades +namespace PepperDash.Essentials.Devices.Common.Shades { - ShadeControllerConfigProperties Config; - /// - /// List of shades to be controlled by this controller + /// Class that contains the shades to be controlled in a room /// - public List Shades { get; private set; } - - /// - /// Constructor for ShadeController - /// - /// - /// - /// - public ShadeController(string key, string name, ShadeControllerConfigProperties config) - : base(key, name) + public class ShadeController : EssentialsDevice, IShades { - Config = config; + ShadeControllerConfigProperties Config; - Shades = new List(); - } + /// + /// Gets the collection of shades controlled by this controller + /// + public List Shades { get; private set; } - /// - protected override bool CustomActivate() - { - foreach (var shadeConfig in Config.Shades) + /// + /// Initializes a new instance of the ShadeController class + /// + /// The device key + /// The device name + /// The shade controller configuration + public ShadeController(string key, string name, ShadeControllerConfigProperties config) + : base(key, name) { - var shade = DeviceManager.GetDeviceForKey(shadeConfig.Key) as ShadeBase; + Config = config; - if (shade != null) - { - AddShade(shade); - } + Shades = new List(); + } + + /// + /// CustomActivate method + /// + /// + public override bool CustomActivate() + { + foreach (var shadeConfig in Config.Shades) + { + var shade = DeviceManager.GetDeviceForKey(shadeConfig.Key) as ShadeBase; + + if (shade != null) + { + AddShade(shade); + } + } + return base.CustomActivate(); + } + + void AddShade(IShadesOpenCloseStop shade) + { + Shades.Add(shade); } - return base.CustomActivate(); } - void AddShade(IShadesOpenCloseStop shade) - { - Shades.Add(shade); - } -} - -/// -/// Class representing the properties for the ShadeController device, including a list of shades to control -/// -public class ShadeControllerConfigProperties -{ /// - /// List of shades to control, represented by their unique keys + /// Represents a ShadeControllerConfigProperties /// - public List Shades { get; set; } - - /// - /// Class representing the configuration for an individual shade, including its unique key - /// - public class ShadeConfig : IKeyed + public class ShadeControllerConfigProperties { /// - /// The unique key of the shade device to be controlled + /// Gets or sets the Shades /// - public string Key { get; set; } - } -} + public List Shades { get; set; } + + + /// + /// Represents a ShadeConfig + /// + public class ShadeConfig + { + /// + /// Gets or sets the Key + /// + public string Key { get; set; } + } + } -/// -/// Factory for creating ShadeController devices -/// -public class ShadeControllerFactory : EssentialsDeviceFactory -{ /// - /// Constructor for ShadeControllerFactory + /// Represents a ShadeControllerFactory /// - public ShadeControllerFactory() + public class ShadeControllerFactory : EssentialsDeviceFactory { - TypeNames = new List() { "shadecontroller" }; + /// + /// Initializes a new instance of the ShadeControllerFactory class + /// + public ShadeControllerFactory() + { + TypeNames = new List() { "shadecontroller" }; + } + + /// + /// BuildDevice method + /// + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new ShadeController Device"); + var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); + + return new ShadeController(dc.Key, dc.Name, props); + } } - /// - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new ShadeController Device"); - var props = Newtonsoft.Json.JsonConvert.DeserializeObject(dc.Properties.ToString()); - - return new ShadeController(dc.Key, dc.Name, props); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs b/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs index c27502d4..afe47814 100644 --- a/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs +++ b/src/PepperDash.Essentials.Devices.Common/SoftCodec/BlueJeansPc.cs @@ -1,231 +1,195 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using Crestron.SimplSharp; + using PepperDash.Core; -using PepperDash.Core.Logging; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Devices.Common.Sources; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.SoftCodec; - -/// -/// Class representing a BlueJeans soft codec running on an in-room PC. -/// -public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSinkWithFeedback +namespace PepperDash.Essentials.Devices.Common.SoftCodec { - /// - /// The input port for any video source. + /// Represents a BlueJeansPc /// - public RoutingInputPort AnyVideoIn { get; private set; } - - /// - /// The currently active input port, which for this device is always AnyVideoIn - /// This is used by the routing system to determine where to route video sources when this device is a destination - /// - public RoutingInputPort CurrentInputPort => AnyVideoIn; - - /// - public event InputChangedEventHandler InputChanged; - - /// - public void ExecuteSwitch(object inputSelector) { } - - /// - public Dictionary CurrentSources { get; private set; } - - /// - public Dictionary CurrentSourceKeys { get; private set; } - - /// - public event EventHandler CurrentSourcesChanged; - - #region IRoutingInputs Members - - /// - /// Collection of the input ports for this device - /// - public RoutingPortCollection InputPorts { get; private set; } - - #endregion - - /// - /// Initializes a new instance of the class. - /// - /// The key for the device. - /// The name of the device. - public BlueJeansPc(string key, string name) - : base(key, name) + public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink { - InputPorts = new RoutingPortCollection - { - (AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this)) - }; - CurrentSources = new Dictionary + /// + /// Gets or sets the AnyVideoIn + /// + public RoutingInputPort AnyVideoIn { get; private set; } + + /// + /// Gets the CurrentInputPort + /// + public RoutingInputPort CurrentInputPort => AnyVideoIn; + + #region IRoutingInputs Members + + /// + /// Gets or sets the InputPorts + /// + public RoutingPortCollection InputPorts { get; private set; } + + #endregion + + /// + /// Initializes a new instance of the class + /// + /// The device key + /// The device name + public BlueJeansPc(string key, string name) + : base(key, name) + { + InputPorts = new RoutingPortCollection { - { eRoutingSignalType.Audio, null }, - { eRoutingSignalType.Video, null }, + (AnyVideoIn = new RoutingInputPort(RoutingPortNames.AnyVideoIn, eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.None, 0, this)) }; - - CurrentSourceKeys = new Dictionary - { - { eRoutingSignalType.Audio, string.Empty }, - { eRoutingSignalType.Video, string.Empty }, - }; - } - - /// - public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - foreach (eRoutingSignalType type in Enum.GetValues(typeof(eRoutingSignalType))) - { - var flagValue = Convert.ToInt32(type); - // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). - // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. - if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - if (!signalType.HasFlag(type)) - { - this.LogDebug("Skipping {type}", type); - continue; - } - - this.LogDebug("setting {type}", type); - - CurrentSources.TryGetValue(type, out var previousSource); - - UpdateCurrentSources(type, previousSource, sourceDevice); - } - } - - private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice) - { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceDevice; - } - else - { - CurrentSources.Add(signalType, sourceDevice); } - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) + #region IRunRouteAction Members + + /// + /// RunRouteAction method + /// + public void RunRouteAction(string routeKey, string sourceListKey) { - CurrentSourceKeys[signalType] = sourceDevice?.Key; - } - else - { - CurrentSourceKeys.Add(signalType, sourceDevice?.Key); + RunRouteAction(routeKey, sourceListKey, null); } - // Raise the CurrentSourcesChanged event - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - - #region IRunRouteAction Members - - /// - /// Runs a route action for the specified route key and source list key. Optionally, a callback can be provided to be executed upon successful completion. - /// - /// - /// - public void RunRouteAction(string routeKey, string sourceListKey) - { - RunRouteAction(routeKey, sourceListKey, null); - } - - /// - /// Runs a route action for the specified route key and source list key. Optionally, a callback can be provided to be executed upon successful completion. - /// - /// - /// - /// - public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback) - { - Task.Run(() => - { - Debug.LogMessage(LogEventLevel.Debug, this, "Run route action '{0}' on SourceList: {1}", routeKey, sourceListKey); - - var dict = ConfigReader.ConfigObject.GetSourceListForKey(sourceListKey); - if (dict == null) + /// + /// RunRouteAction method + /// + public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback) + { + CrestronInvoke.BeginInvoke(o => { - Debug.LogMessage(LogEventLevel.Debug, this, "WARNING: Config source list '{0}' not found", sourceListKey); - return; - } + Debug.LogMessage(LogEventLevel.Debug, this, "Run route action '{0}' on SourceList: {1}", routeKey, sourceListKey); - // Try to get the list item by it's string key - if (!dict.ContainsKey(routeKey)) - { - Debug.LogMessage(LogEventLevel.Debug, this, "WARNING: No item '{0}' found on config list '{1}'", - routeKey, sourceListKey); - return; - } + var dict = ConfigReader.ConfigObject.GetSourceListForKey(sourceListKey); + if (dict == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "WARNING: Config source list '{0}' not found", sourceListKey); + return; + } - var item = dict[routeKey]; + // Try to get the list item by it's string key + if (!dict.ContainsKey(routeKey)) + { + Debug.LogMessage(LogEventLevel.Debug, this, "WARNING: No item '{0}' found on config list '{1}'", + routeKey, sourceListKey); + return; + } - foreach (var route in item.RouteList) - { - DoRoute(route); - } + var item = dict[routeKey]; - // store the name and UI info for routes - // CurrentSourceInfo tracking removed in v3 interface consolidation + foreach (var route in item.RouteList) + { + DoRoute(route); + } - // report back when done - if (successCallback != null) - successCallback(); - }); - } + // store the name and UI info for routes + if (item.SourceKey == "none") + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = null; + } + else if (item.SourceKey != null) + { + CurrentSourceInfoKey = routeKey; + CurrentSourceInfo = item; + } - #endregion - - /// - /// - /// - /// - /// - bool DoRoute(SourceRouteListItem route) - { - IRoutingSinkWithFeedback dest = null; - - dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSinkWithFeedback; - - if (dest == null) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Cannot route, unknown destination '{0}'", route.DestinationKey); - return false; + // report back when done + if (successCallback != null) + successCallback(); + }); } - if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) + #endregion + + /// + /// + /// + /// + /// + bool DoRoute(SourceRouteListItem route) { - dest.ReleaseRoute(); - if (dest is IHasPowerControl) - (dest as IHasPowerControl).PowerOff(); - } - else - { - var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs; - if (source == null) + IRoutingSink dest = null; + + dest = DeviceManager.GetDeviceForKey(route.DestinationKey) as IRoutingSink; + + if (dest == null) { - Debug.LogMessage(LogEventLevel.Debug, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey); + Debug.LogMessage(LogEventLevel.Debug, this, "Cannot route, unknown destination '{0}'", route.DestinationKey); return false; } - dest.ReleaseAndMakeRoute(source, route.Type); + + if (route.SourceKey.Equals("$off", StringComparison.OrdinalIgnoreCase)) + { + dest.ReleaseRoute(); + if (dest is IHasPowerControl) + (dest as IHasPowerControl).PowerOff(); + } + else + { + var source = DeviceManager.GetDeviceForKey(route.SourceKey) as IRoutingOutputs; + if (source == null) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Cannot route unknown source '{0}' to {1}", route.SourceKey, route.DestinationKey); + return false; + } + dest.ReleaseAndMakeRoute(source, route.Type); + } + return true; } - return true; + + + + #region IHasCurrentSourceInfoChange Members + + /// + /// Gets or sets the CurrentSourceInfoKey + /// + public string CurrentSourceInfoKey { get; set; } + + /// + /// The SourceListItem last run - containing names and icons + /// + public SourceListItem CurrentSourceInfo + { + get { return _CurrentSourceInfo; } + set + { + if (value == _CurrentSourceInfo) return; + + var handler = CurrentSourceChange; + // remove from in-use tracker, if so equipped + if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) + (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.RemoveUser(this, "control"); + + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); + + _CurrentSourceInfo = value; + + // add to in-use tracking + if (_CurrentSourceInfo != null && _CurrentSourceInfo.SourceDevice is IInUseTracking) + (_CurrentSourceInfo.SourceDevice as IInUseTracking).InUseTracker.AddUser(this, "control"); + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } + } + SourceListItem _CurrentSourceInfo; + + /// + /// Event fired when the current source changes + /// + public event SourceInfoChangeHandler CurrentSourceChange; + + #endregion } - - - -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs index e13e3804..f8a8f640 100644 --- a/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs +++ b/src/PepperDash.Essentials.Devices.Common/SoftCodec/GenericSoftCodec.cs @@ -1,174 +1,134 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using PepperDash.Core; -using PepperDash.Core.Logging; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Routing; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.SoftCodec; - - -/// -/// Represents a GenericSoftCodec -/// -public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithFeedback +namespace PepperDash.Essentials.Devices.Common.SoftCodec { - private RoutingInputPort _currentInputPort; - /// - /// Gets or sets the CurrentInputPort + /// Represents a GenericSoftCodec /// - public RoutingInputPort CurrentInputPort + public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithSwitchingWithInputPort { - get => _currentInputPort; - set + private RoutingInputPort _currentInputPort; + + /// + /// Gets or sets the CurrentInputPort + /// + public RoutingInputPort CurrentInputPort { - _currentInputPort = value; - - InputChanged?.Invoke(this, _currentInputPort); - } - } - - /// - public Dictionary CurrentSources { get; private set; } - - /// - public Dictionary CurrentSourceKeys { get; private set; } - - /// - public event System.EventHandler CurrentSourcesChanged; - - /// - /// Initializes a new instance of the class - /// - /// The device key - /// The device name - /// The device properties - public GenericSoftCodec(string key, string name, GenericSoftCodecProperties props) : base(key, name) - { - for (var i = 1; i <= props.OutputCount; i++) - { - var outputPort = new RoutingOutputPort($"output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); - - OutputPorts.Add(outputPort); - } - - for (var i = 1; i <= props.ContentInputCount; i++) - { - var inputPort = new RoutingInputPort($"contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this); - - InputPorts.Add(inputPort); - } - - CurrentSources = new Dictionary + get => _currentInputPort; + set { - { eRoutingSignalType.Audio, null }, - { eRoutingSignalType.Video, null }, - }; + _currentInputPort = value; - CurrentSourceKeys = new Dictionary + InputChanged?.Invoke(this, _currentInputPort); + } + } + + /// + /// Initializes a new instance of the class + /// + /// The device key + /// The device name + /// The device properties + public GenericSoftCodec(string key, string name, GenericSoftCodecProperties props) : base(key, name) + { + InputPorts = new RoutingPortCollection(); + OutputPorts = new RoutingPortCollection(); + + for (var i = 1; i <= props.OutputCount; i++) { - { eRoutingSignalType.Audio, string.Empty }, - { eRoutingSignalType.Video, string.Empty }, - }; + var outputPort = new RoutingOutputPort($"output{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, null, this); - if (!props.HasCameraInputs) - { - return; + OutputPorts.Add(outputPort); + } + + for (var i = 1; i <= props.ContentInputCount; i++) + { + var inputPort = new RoutingInputPort($"contentInput{i}", eRoutingSignalType.AudioVideo, eRoutingPortConnectionType.Hdmi, $"contentInput{i}", this); + + InputPorts.Add(inputPort); + } + + if (!props.HasCameraInputs) + { + return; + } + + for (var i = 1; i <= props.CameraInputCount; i++) + { + var cameraPort = new RoutingInputPort($"cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this); + + InputPorts.Add(cameraPort); + } } - for (var i = 1; i <= props.CameraInputCount; i++) + /// + /// Gets or sets the InputPorts + /// + public RoutingPortCollection InputPorts { get; private set; } + + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + /// + /// Gets or sets the CurrentSourceInfoKey + /// + public string CurrentSourceInfoKey { get; set; } + + /// + /// Gets or sets the CurrentSourceInfo + /// + public SourceListItem CurrentSourceInfo { - var cameraPort = new RoutingInputPort($"cameraInput{i}", eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, $"cameraInput{i}", this); + get + { + return _CurrentSourceInfo; + } + set + { + if (value == _CurrentSourceInfo) return; - InputPorts.Add(cameraPort); - } - } + var handler = CurrentSourceChange; - /// - public virtual void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - foreach (eRoutingSignalType type in System.Enum.GetValues(typeof(eRoutingSignalType))) - { - var flagValue = System.Convert.ToInt32(type); - // Skip if flagValue is 0 or not a power of two (i.e., not a single-bit flag). - // (flagValue & (flagValue - 1)) != 0 checks if more than one bit is set. - if (flagValue == 0 || (flagValue & (flagValue - 1)) != 0) - { - this.LogDebug("Skipping {type}", type); - continue; - } + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.WillChange); - if (!signalType.HasFlag(type)) - { - this.LogDebug("Skipping {type}", type); - continue; - } + _CurrentSourceInfo = value; - this.LogDebug("setting {type}", type); - - CurrentSources.TryGetValue(type, out var previousSource); - - UpdateCurrentSources(type, previousSource, sourceDevice); - } - } - - private void UpdateCurrentSources(eRoutingSignalType signalType, IRoutingSource previousSource, IRoutingSource sourceDevice) - { - if (CurrentSources.ContainsKey(signalType)) - { - CurrentSources[signalType] = sourceDevice; - } - else - { - CurrentSources.Add(signalType, sourceDevice); - } - - // Update the current source key for the specified signal type - if (CurrentSourceKeys.ContainsKey(signalType)) - { - CurrentSourceKeys[signalType] = sourceDevice?.Key; - } - else - { - CurrentSourceKeys.Add(signalType, sourceDevice?.Key); - } - - // Raise the CurrentSourcesChanged event - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - - /// - /// Gets or sets the InputPorts - /// - public RoutingPortCollection InputPorts { get; private set; } - - /// - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - /// - /// Event fired when the input changes - /// - public event InputChangedEventHandler InputChanged; - - /// - /// ExecuteSwitch method - /// - public void ExecuteSwitch(object inputSelector) - { - var inputPort = InputPorts.FirstOrDefault(p => p.Selector == inputSelector); - - if (inputPort == null) - { - Debug.LogMessage(LogEventLevel.Warning, "No input port found for selector {inputSelector}", inputSelector); - return; + if (handler != null) + handler(_CurrentSourceInfo, ChangeType.DidChange); + } } - CurrentInputPort = inputPort; + SourceListItem _CurrentSourceInfo; + + /// + /// Event fired when the current source changes + /// + public event SourceInfoChangeHandler CurrentSourceChange; + + /// + /// Event fired when the input changes + /// + public event InputChangedEventHandler InputChanged; + + /// + /// ExecuteSwitch method + /// + public void ExecuteSwitch(object inputSelector) + { + var inputPort = InputPorts.FirstOrDefault(p => p.Selector == inputSelector); + + if (inputPort == null) + { + Debug.LogMessage(LogEventLevel.Warning, "No input port found for selector {inputSelector}", inputSelector); + return; + } + + CurrentInputPort = inputPort; + } } } - diff --git a/src/PepperDash.Essentials.Devices.Common/SomeOtherWebSocketClass.cs b/src/PepperDash.Essentials.Devices.Common/SomeOtherWebSocketClass.cs deleted file mode 100644 index 675775a4..00000000 --- a/src/PepperDash.Essentials.Devices.Common/SomeOtherWebSocketClass.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -public class SomeOtherWebSocketClass -{ - private ClientWebSocket _webSocket; - - public SomeOtherWebSocketClass() - { - _webSocket = new ClientWebSocket(); - } - - public async Task ConnectAsync(Uri uri) - { - await _webSocket.ConnectAsync(uri, CancellationToken.None); - } - - public async Task SendAsync(string message) - { - var buffer = System.Text.Encoding.UTF8.GetBytes(message); - await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); - } - - public async Task ReceiveAsync() - { - var buffer = new byte[1024]; - var result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); - } - - public async Task CloseAsync() - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - } -} diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs index d12dd37a..e2ef1c09 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/InRoomPc.cs @@ -1,64 +1,83 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Sources; - -public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking +namespace PepperDash.Essentials.Devices.Common.Sources { - public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } - public string IconName { get; set; } - public BoolFeedback HasPowerOnFeedback { get; private set; } - /// - /// Gets or sets the AnyVideoOut + /// Represents a InRoomPc /// - public RoutingOutputPort AnyVideoOut { get; private set; } - - #region IRoutingOutputs Members - - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - #endregion - - /// - /// Initializes a new instance of the class - /// - /// - /// - public InRoomPc(string key, string name) - : base(key, name) + public class InRoomPc : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { - IconName = "PC"; - HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback", - () => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus); + /// + /// Gets or sets the DisplayUiType + /// + public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } + /// + /// Gets or sets the IconName + /// + public string IconName { get; set; } + /// + /// Gets or sets the HasPowerOnFeedback + /// + public BoolFeedback HasPowerOnFeedback { get; private set; } - OutputPorts = new RoutingPortCollection + /// + /// Gets or sets the AnyVideoOut + /// + public RoutingOutputPort AnyVideoOut { get; private set; } + + #region IRoutingOutputs Members + + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + + #endregion + + /// + /// Initializes a new instance of the class + /// + /// + /// + public InRoomPc(string key, string name) + : base(key, name) + { + IconName = "PC"; + HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback", + () => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus); + + OutputPorts = new RoutingPortCollection { (AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyVideoOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.None, 0, this)) }; - } - - /// - /// Passes through the VideoStatuses list - /// - public FeedbackCollection Feedbacks - { - get - { - var newList = new FeedbackCollection(); - newList.AddRange(this.GetVideoStatuses().ToList()); - return newList; } + + #region IHasFeedback Members + + /// + /// Passes through the VideoStatuses list + /// + public FeedbackCollection Feedbacks + { + get + { + var newList = new FeedbackCollection(); + newList.AddRange(this.GetVideoStatuses().ToList()); + return newList; + } + } + + #endregion + + #region IUsageTracking Members + + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } + + #endregion } - - #region IUsageTracking Members - - public UsageTracking UsageTracker { get; set; } - - #endregion -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs index 0a7466e7..83ccfc4c 100644 --- a/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs +++ b/src/PepperDash.Essentials.Devices.Common/Sources/Laptop.cs @@ -1,68 +1,83 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.Sources; - -public class Laptop : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking +namespace PepperDash.Essentials.Devices.Common.Sources { - public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } - public string IconName { get; set; } - public BoolFeedback HasPowerOnFeedback { get; private set; } - /// - /// Gets or sets the AnyVideoOut + /// Represents a Laptop /// - public RoutingOutputPort AnyVideoOut { get; private set; } - - #region IRoutingOutputs Members - - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - #endregion - - /// - /// Initializes a new instance of the Laptop class - /// - /// The device key - /// The device name - public Laptop(string key, string name) - : base(key, name) + public class Laptop : EssentialsDevice, IHasFeedback, IRoutingSource, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking { - IconName = "Laptop"; + /// + /// Gets or sets the DisplayUiType + /// + public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } } + /// + /// Gets or sets the IconName + /// + public string IconName { get; set; } + /// + /// Gets or sets the HasPowerOnFeedback + /// + public BoolFeedback HasPowerOnFeedback { get; private set; } - HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback", - () => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus); + /// + /// Gets or sets the AnyVideoOut + /// + public RoutingOutputPort AnyVideoOut { get; private set; } - OutputPorts = new RoutingPortCollection + #region IRoutingOutputs Members + + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + + #endregion + + /// + /// Initializes a new instance of the Laptop class + /// + /// The device key + /// The device name + public Laptop(string key, string name) + : base(key, name) { - (AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.None, 0, this)) - }; - } + IconName = "Laptop"; - #region IHasFeedback Members + HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback", + () => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus); - /// - /// Passes through the VideoStatuses list - /// - public FeedbackCollection Feedbacks - { - get - { - var newList = new FeedbackCollection(); - newList.AddRange(this.GetVideoStatuses().ToList()); - return newList; + OutputPorts = new RoutingPortCollection + { + (AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.None, 0, this)) + }; } + + #region IHasFeedback Members + + /// + /// Passes through the VideoStatuses list + /// + public FeedbackCollection Feedbacks + { + get + { + var newList = new FeedbackCollection(); + newList.AddRange(this.GetVideoStatuses().ToList()); + return newList; + } + } + + #endregion + + #region IUsageTracking Members + + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } + + #endregion } - - #endregion - - #region IUsageTracking Members - - public UsageTracking UsageTracker { get; set; } - - #endregion -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs index 453b6aa0..6cf11c54 100644 --- a/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs +++ b/src/PepperDash.Essentials.Devices.Common/Streaming/AppleTV.cs @@ -7,244 +7,244 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common; - -/// -/// Represents a AppleTV -/// Wrapper class for an IR-Controlled AppleTV -/// -[Description("Wrapper class for an IR-Controlled AppleTV")] -public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs +namespace PepperDash.Essentials.Devices.Common { /// - /// Gets or sets the IrPort + /// Represents a AppleTV + /// Wrapper class for an IR-Controlled AppleTV /// - public IrOutputPortController IrPort { get; private set; } + [Description("Wrapper class for an IR-Controlled AppleTV")] + public class AppleTV : EssentialsBridgeableDevice, IDPad, ITransport, IUiDisplayInfo, IRoutingSource, IRoutingOutputs - /// - /// Standard Driver Name - /// - public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir"; - /// - /// Gets or sets the DisplayUiType - /// - public uint DisplayUiType { get { return DisplayUiConstants.TypeAppleTv; } } - - /// - /// Initializes a new instance of the class - /// - /// The device key - /// The device name - /// The IR output port controller - public AppleTV(string key, string name, IrOutputPortController portCont) - : base(key, name) { - IrPort = portCont; - DeviceManager.AddDevice(portCont); + /// + /// Gets or sets the IrPort + /// + public IrOutputPortController IrPort { get; private set; } - HdmiOut = new RoutingOutputPort(RoutingPortNames.HdmiOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, - eRoutingPortConnectionType.Hdmi, null, this); - AnyAudioOut = new RoutingOutputPort(RoutingPortNames.AnyAudioOut, eRoutingSignalType.Audio, - eRoutingPortConnectionType.DigitalAudio, null, this); - OutputPorts = new RoutingPortCollection { HdmiOut, AnyAudioOut }; + /// + /// Standard Driver Name + /// + public const string StandardDriverName = "Apple_AppleTV_4th_Gen_Essentials.ir"; + /// + /// Gets or sets the DisplayUiType + /// + public uint DisplayUiType { get { return DisplayUiConstants.TypeAppleTv; } } - PrintExpectedIrCommands(); - } - - /// - /// PrintExpectedIrCommands method - /// - public void PrintExpectedIrCommands() - { - var cmds = typeof(AppleTvIrCommands).GetFields(BindingFlags.Public | BindingFlags.Static); - - foreach (var value in cmds.Select(cmd => cmd.GetValue(null)).OfType()) + /// + /// Initializes a new instance of the class + /// + /// The device key + /// The device name + /// The IR output port controller + public AppleTV(string key, string name, IrOutputPortController portCont) + : base(key, name) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Expected IR Function Name: {0}", value); - } - } + IrPort = portCont; + DeviceManager.AddDevice(portCont); + HdmiOut = new RoutingOutputPort(RoutingPortNames.HdmiOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, + eRoutingPortConnectionType.Hdmi, null, this); + AnyAudioOut = new RoutingOutputPort(RoutingPortNames.AnyAudioOut, eRoutingSignalType.Audio, + eRoutingPortConnectionType.DigitalAudio, null, this); + OutputPorts = new RoutingPortCollection { HdmiOut, AnyAudioOut }; - #region IDPad Members - - /// - /// Up method - /// - public void Up(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Up, pressRelease); - } - - /// - /// Down method - /// - public void Down(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Down, pressRelease); - } - - /// - /// Left method - /// - public void Left(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Left, pressRelease); - } - - /// - /// Right method - /// - public void Right(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Right, pressRelease); - } - - /// - /// Select method - /// - public void Select(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Enter, pressRelease); - } - - /// - /// Menu method - /// - public void Menu(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.Menu, pressRelease); - } - - /// - /// Exit method - /// - public void Exit(bool pressRelease) - { - - } - - #endregion - - #region ITransport Members - - /// - /// Play method - /// - public void Play(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.PlayPause, pressRelease); - } - - /// - /// Pause method - /// - public void Pause(bool pressRelease) - { - IrPort.PressRelease(AppleTvIrCommands.PlayPause, pressRelease); - } - - /// - /// Not implemented - /// - /// - /// - /// Rewind method - /// - public void Rewind(bool pressRelease) - { - } - - /// - /// Not implemented - /// - /// - public void FFwd(bool pressRelease) - { - } - - /// - /// Not implemented - /// - /// - public void ChapMinus(bool pressRelease) - { - } - - /// - /// Not implemented - /// - /// - public void ChapPlus(bool pressRelease) - { - } - - /// - /// Not implemented - /// - /// - public void Stop(bool pressRelease) - { - } - - /// - /// Not implemented - /// - /// - public void Record(bool pressRelease) - { - } - - #endregion - - #region IRoutingOutputs Members - - /// - /// Gets the HdmiOut - /// - public RoutingOutputPort HdmiOut { get; private set; } - - /// - /// Gets the AnyAudioOut - /// - public RoutingOutputPort AnyAudioOut { get; private set; } - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - #endregion - - /// - /// LinkToApi method - /// - public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - var joinMap = new AppleTvJoinMap(joinStart); - - var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); - - if (!string.IsNullOrEmpty(joinMapSerialized)) - joinMap = JsonConvert.DeserializeObject(joinMapSerialized); - - if (bridge != null) - { - bridge.AddJoinMap(Key, joinMap); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + PrintExpectedIrCommands(); } - Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); - Debug.LogMessage(LogEventLevel.Information, "Linking to Bridge Type {0}", GetType().Name); + /// + /// PrintExpectedIrCommands method + /// + public void PrintExpectedIrCommands() + { + var cmds = typeof(AppleTvIrCommands).GetFields(BindingFlags.Public | BindingFlags.Static); - trilist.SetBoolSigAction(joinMap.UpArrow.JoinNumber, Up); - trilist.SetBoolSigAction(joinMap.DnArrow.JoinNumber, Down); - trilist.SetBoolSigAction(joinMap.LeftArrow.JoinNumber, Left); - trilist.SetBoolSigAction(joinMap.RightArrow.JoinNumber, Right); - trilist.SetBoolSigAction(joinMap.Select.JoinNumber, Select); - trilist.SetBoolSigAction(joinMap.Menu.JoinNumber, Menu); - trilist.SetBoolSigAction(joinMap.PlayPause.JoinNumber, Play); + foreach (var value in cmds.Select(cmd => cmd.GetValue(null)).OfType()) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Expected IR Function Name: {0}", value); + } + } + + #region IDPad Members + + /// + /// Up method + /// + public void Up(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Up, pressRelease); + } + + /// + /// Down method + /// + public void Down(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Down, pressRelease); + } + + /// + /// Left method + /// + public void Left(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Left, pressRelease); + } + + /// + /// Right method + /// + public void Right(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Right, pressRelease); + } + + /// + /// Select method + /// + public void Select(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Enter, pressRelease); + } + + /// + /// Menu method + /// + public void Menu(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.Menu, pressRelease); + } + + /// + /// Exit method + /// + public void Exit(bool pressRelease) + { + + } + + #endregion + + #region ITransport Members + + /// + /// Play method + /// + public void Play(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.PlayPause, pressRelease); + } + + /// + /// Pause method + /// + public void Pause(bool pressRelease) + { + IrPort.PressRelease(AppleTvIrCommands.PlayPause, pressRelease); + } + + /// + /// Not implemented + /// + /// + /// + /// Rewind method + /// + public void Rewind(bool pressRelease) + { + } + + /// + /// Not implemented + /// + /// + public void FFwd(bool pressRelease) + { + } + + /// + /// Not implemented + /// + /// + public void ChapMinus(bool pressRelease) + { + } + + /// + /// Not implemented + /// + /// + public void ChapPlus(bool pressRelease) + { + } + + /// + /// Not implemented + /// + /// + public void Stop(bool pressRelease) + { + } + + /// + /// Not implemented + /// + /// + public void Record(bool pressRelease) + { + } + + #endregion + + #region IRoutingOutputs Members + + /// + /// Gets the HdmiOut + /// + public RoutingOutputPort HdmiOut { get; private set; } + + /// + /// Gets the AnyAudioOut + /// + public RoutingOutputPort AnyAudioOut { get; private set; } + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + + #endregion + + /// + /// LinkToApi method + /// + public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + var joinMap = new AppleTvJoinMap(joinStart); + + var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey); + + if (!string.IsNullOrEmpty(joinMapSerialized)) + joinMap = JsonConvert.DeserializeObject(joinMapSerialized); + + if (bridge != null) + { + bridge.AddJoinMap(Key, joinMap); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device."); + } + + Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X")); + Debug.LogMessage(LogEventLevel.Information, "Linking to Bridge Type {0}", GetType().Name); + + trilist.SetBoolSigAction(joinMap.UpArrow.JoinNumber, Up); + trilist.SetBoolSigAction(joinMap.DnArrow.JoinNumber, Down); + trilist.SetBoolSigAction(joinMap.LeftArrow.JoinNumber, Left); + trilist.SetBoolSigAction(joinMap.RightArrow.JoinNumber, Right); + trilist.SetBoolSigAction(joinMap.Select.JoinNumber, Select); + trilist.SetBoolSigAction(joinMap.Menu.JoinNumber, Menu); + trilist.SetBoolSigAction(joinMap.PlayPause.JoinNumber, Play); + } } -} - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/CallHistoryDataClasses.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/CallHistoryDataClasses.cs index 8f4f1b5b..8872172a 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/CallHistoryDataClasses.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/CallHistoryDataClasses.cs @@ -1,220 +1,220 @@ using System; using System.Collections.Generic; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Represents a CiscoCallHistory -/// -public class CiscoCallHistory +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Represents a CallbackNumber + /// Represents a CiscoCallHistory /// - public class CallbackNumber + public class CiscoCallHistory { /// - /// Gets or sets the Value + /// Represents a CallbackNumber /// - public string Value { get; set; } - } + public class CallbackNumber + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a DisplayName - /// - public class DisplayName - { /// - /// Gets or sets the Value + /// Represents a DisplayName /// - public string Value { get; set; } - } + public class DisplayName + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a LastOccurrenceStartTime - /// - public class LastOccurrenceStartTime - { /// - /// Gets or sets the Value + /// Represents a LastOccurrenceStartTime /// - public DateTime Value { get; set; } - } + public class LastOccurrenceStartTime + { + /// + /// Gets or sets the Value + /// + public DateTime Value { get; set; } + } - /// - /// Represents a LastOccurrenceDaysAgo - /// - public class LastOccurrenceDaysAgo - { /// - /// Gets or sets the Value + /// Represents a LastOccurrenceDaysAgo /// - public string Value { get; set; } - } + public class LastOccurrenceDaysAgo + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a LastOccurrenceHistoryId - /// - public class LastOccurrenceHistoryId - { /// - /// Gets or sets the Value + /// Represents a LastOccurrenceHistoryId /// - public string Value { get; set; } - } + public class LastOccurrenceHistoryId + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a OccurrenceType - /// - public class OccurrenceType - { /// - /// Gets or sets the Value + /// Represents a OccurrenceType /// - public string Value { get; set; } - } + public class OccurrenceType + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a IsAcknowledged - /// - public class IsAcknowledged - { /// - /// Gets or sets the Value + /// Represents a IsAcknowledged /// - public string Value { get; set; } - } + public class IsAcknowledged + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a OccurrenceCount - /// - public class OccurrenceCount - { /// - /// Gets or sets the Value + /// Represents a OccurrenceCount /// - public string Value { get; set; } - } + public class OccurrenceCount + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a Entry - /// - public class Entry - { /// - /// Gets or sets the id + /// Represents a Entry /// - public string id { get; set; } - /// - /// Gets or sets the CallbackNumber - /// - public CallbackNumber CallbackNumber { get; set; } - /// - /// Gets or sets the DisplayName - /// - public DisplayName DisplayName { get; set; } - /// - /// Gets or sets the LastOccurrenceStartTime - /// - public LastOccurrenceStartTime LastOccurrenceStartTime { get; set; } - /// - /// Gets or sets the LastOccurrenceDaysAgo - /// - public LastOccurrenceDaysAgo LastOccurrenceDaysAgo { get; set; } - /// - /// Gets or sets the LastOccurrenceHistoryId - /// - public LastOccurrenceHistoryId LastOccurrenceHistoryId { get; set; } - /// - /// Gets or sets the OccurrenceType - /// - public OccurrenceType OccurrenceType { get; set; } - /// - /// Gets or sets the IsAcknowledged - /// - public IsAcknowledged IsAcknowledged { get; set; } - /// - /// Gets or sets the OccurrenceCount - /// - public OccurrenceCount OccurrenceCount { get; set; } - } + public class Entry + { + /// + /// Gets or sets the id + /// + public string id { get; set; } + /// + /// Gets or sets the CallbackNumber + /// + public CallbackNumber CallbackNumber { get; set; } + /// + /// Gets or sets the DisplayName + /// + public DisplayName DisplayName { get; set; } + /// + /// Gets or sets the LastOccurrenceStartTime + /// + public LastOccurrenceStartTime LastOccurrenceStartTime { get; set; } + /// + /// Gets or sets the LastOccurrenceDaysAgo + /// + public LastOccurrenceDaysAgo LastOccurrenceDaysAgo { get; set; } + /// + /// Gets or sets the LastOccurrenceHistoryId + /// + public LastOccurrenceHistoryId LastOccurrenceHistoryId { get; set; } + /// + /// Gets or sets the OccurrenceType + /// + public OccurrenceType OccurrenceType { get; set; } + /// + /// Gets or sets the IsAcknowledged + /// + public IsAcknowledged IsAcknowledged { get; set; } + /// + /// Gets or sets the OccurrenceCount + /// + public OccurrenceCount OccurrenceCount { get; set; } + } - /// - /// Represents a Offset - /// - public class Offset - { /// - /// Gets or sets the Value + /// Represents a Offset /// - public string Value { get; set; } - } + public class Offset + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a Limit - /// - public class Limit - { /// - /// Gets or sets the Value + /// Represents a Limit /// - public string Value { get; set; } - } + public class Limit + { + /// + /// Gets or sets the Value + /// + public string Value { get; set; } + } - /// - /// Represents a ResultInfo - /// - public class ResultInfo - { /// - /// Gets or sets the Offset + /// Represents a ResultInfo /// - public Offset Offset { get; set; } - /// - /// Gets or sets the Limit - /// - public Limit Limit { get; set; } - } + public class ResultInfo + { + /// + /// Gets or sets the Offset + /// + public Offset Offset { get; set; } + /// + /// Gets or sets the Limit + /// + public Limit Limit { get; set; } + } - /// - /// Represents a CallHistoryRecentsResult - /// - public class CallHistoryRecentsResult - { /// - /// Gets or sets the status + /// Represents a CallHistoryRecentsResult /// - public string status { get; set; } - /// - /// Gets or sets the Entry - /// - public List Entry { get; set; } - /// - /// Gets or sets the ResultInfo - /// - public ResultInfo ResultInfo { get; set; } - } + public class CallHistoryRecentsResult + { + /// + /// Gets or sets the status + /// + public string status { get; set; } + /// + /// Gets or sets the Entry + /// + public List Entry { get; set; } + /// + /// Gets or sets the ResultInfo + /// + public ResultInfo ResultInfo { get; set; } + } - /// - /// Represents a CommandResponse - /// - public class CommandResponse - { /// - /// Gets or sets the CallHistoryRecentsResult + /// Represents a CommandResponse /// - public CallHistoryRecentsResult CallHistoryRecentsResult { get; set; } - } + public class CommandResponse + { + /// + /// Gets or sets the CallHistoryRecentsResult + /// + public CallHistoryRecentsResult CallHistoryRecentsResult { get; set; } + } - /// - /// Represents a RootObject - /// - public class RootObject - { /// - /// Gets or sets the CommandResponse + /// Represents a RootObject /// - public CommandResponse CommandResponse { get; set; } + public class RootObject + { + /// + /// Gets or sets the CommandResponse + /// + public CommandResponse CommandResponse { get; set; } + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/RoomPresets.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/RoomPresets.cs index 333c4aa9..e6e2be7e 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/RoomPresets.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/RoomPresets.cs @@ -3,83 +3,84 @@ using System.Collections.Generic; using System.Linq; using PepperDash.Essentials.Core.Presets; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - -/// -/// Interface for camera presets -/// -public interface IHasCodecRoomPresets +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Event that is raised when the list of room presets has changed. + /// Interface for camera presets /// - event EventHandler CodecRoomPresetsListHasChanged; - - /// - /// List of near end presets that can be recalled. - /// - List NearEndPresets { get; } - - /// - /// List of far end presets that can be recalled. - /// - List FarEndRoomPresets { get; } - - /// - /// Selects a near end preset by its ID. - /// - /// - void CodecRoomPresetSelect(int preset); - - /// - /// Stores a near end preset with the given ID and description. - /// - /// - /// - void CodecRoomPresetStore(int preset, string description); - - /// - /// Selects a far end preset by its ID. This is typically used to recall a preset that has been defined on the far end codec. - /// - /// - void SelectFarEndPreset(int preset); -} - -/// -/// Static class for converting non-generic RoomPresets to generic CameraPresets. -/// -public static class RoomPresets -{ - /// - /// Converts non-generic RoomPresets to generic CameraPresets - /// - /// - /// - public static List GetGenericPresets(this List presets) where TSource : ConvertiblePreset where TDestination : PresetBase + public interface IHasCodecRoomPresets { - return - presets.Select(preset => preset.ConvertCodecPreset()) - .Where(newPreset => newPreset != null) - .Cast() - .ToList(); + /// + /// Event that is raised when the list of room presets has changed. + /// + event EventHandler CodecRoomPresetsListHasChanged; + + /// + /// List of near end presets that can be recalled. + /// + List NearEndPresets { get; } + + /// + /// List of far end presets that can be recalled. + /// + List FarEndRoomPresets { get; } + + /// + /// Selects a near end preset by its ID. + /// + /// + void CodecRoomPresetSelect(int preset); + + /// + /// Stores a near end preset with the given ID and description. + /// + /// + /// + void CodecRoomPresetStore(int preset, string description); + + /// + /// Selects a far end preset by its ID. This is typically used to recall a preset that has been defined on the far end codec. + /// + /// + void SelectFarEndPreset(int preset); } -} -/// -/// Represents a room preset on a video codec. Typically stores camera position(s) and video routing. Can be recalled by Far End if enabled. -/// -public class CodecRoomPreset : PresetBase -{ /// - /// + /// Static class for converting non-generic RoomPresets to generic CameraPresets. /// - /// - /// - /// - /// - public CodecRoomPreset(int id, string description, bool def, bool isDef) - : base(id, description, def, isDef) + public static class RoomPresets { + /// + /// Converts non-generic RoomPresets to generic CameraPresets + /// + /// + /// + public static List GetGenericPresets(this List presets) where TSource : ConvertiblePreset where TDestination : PresetBase + { + return + presets.Select(preset => preset.ConvertCodecPreset()) + .Where(newPreset => newPreset != null) + .Cast() + .ToList(); + } + } + /// + /// Represents a CodecRoomPreset + /// + public class CodecRoomPreset : PresetBase + { + /// + /// + /// + /// + /// + /// + /// + public CodecRoomPreset(int id, string description, bool def, bool isDef) + : base(id, description, def, isDef) + { + + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eCommandType.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eCommandType.cs index d33ec5c5..b68f280d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eCommandType.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eCommandType.cs @@ -1,3 +1,4 @@ -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco; - -enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; \ No newline at end of file +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Cisco +{ + enum eCommandType { SessionStart, SessionEnd, Command, GetStatus, GetConfiguration }; +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceMode.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceMode.cs index 446e4138..6d77551d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceMode.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceMode.cs @@ -22,4 +22,4 @@ /// Error } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceType.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceType.cs index bfee4fed..6d5811e3 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceType.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/CiscoCodec/eExternalSourceType.cs @@ -34,4 +34,4 @@ /// other } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/ConvertiblePreset.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/ConvertiblePreset.cs index 7298cfe7..e75f436d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/ConvertiblePreset.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/ConvertiblePreset.cs @@ -1,16 +1,16 @@ using PepperDash.Essentials.Core.Presets; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Base class for presets that can be converted to PresetBase -/// -public abstract class ConvertiblePreset +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Converts the preset to a PresetBase + /// Base class for presets that can be converted to PresetBase /// - /// - public abstract PresetBase ConvertCodecPreset(); -} + public abstract class ConvertiblePreset + { + /// + /// Converts the preset to a PresetBase + /// + /// + public abstract PresetBase ConvertCodecPreset(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecLayouts.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecLayouts.cs index bc4e5a80..61a40048 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecLayouts.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecLayouts.cs @@ -1,29 +1,30 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - -/// -/// Defines the required elements for layout control -/// -public interface IHasCodecLayouts +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Feedback that indicates the current layout on the local display + /// Defines the required elements for layout control /// - StringFeedback LocalLayoutFeedback { get; } + public interface IHasCodecLayouts + { + /// + /// Feedback that indicates the current layout on the local display + /// + StringFeedback LocalLayoutFeedback { get; } - /// - /// Toggles the local layout - /// - void LocalLayoutToggle(); + /// + /// Toggles the local layout + /// + void LocalLayoutToggle(); - /// - /// Toggles the local layout to single prominent - /// - void LocalLayoutToggleSingleProminent(); + /// + /// Toggles the local layout to single prominent + /// + void LocalLayoutToggleSingleProminent(); - /// - /// Toggle the MinMax layout - /// - void MinMaxLayoutToggle(); -} + /// + /// Toggle the MinMax layout + /// + void MinMaxLayoutToggle(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecSelfview.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecSelfview.cs index 80617044..a52670bc 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecSelfview.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasCodecSelfview.cs @@ -1,35 +1,35 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Defines the requred elements for selfview control -/// -public interface IHasCodecSelfView +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Feedback that indicates whether Selfview is on + /// Defines the requred elements for selfview control /// - BoolFeedback SelfviewIsOnFeedback { get; } + public interface IHasCodecSelfView + { + /// + /// Feedback that indicates whether Selfview is on + /// + BoolFeedback SelfviewIsOnFeedback { get; } - /// - /// Setting that indicates whether the device shows selfview by default - /// - bool ShowSelfViewByDefault { get; } + /// + /// Setting that indicates whether the device shows selfview by default + /// + bool ShowSelfViewByDefault { get; } - /// - /// Turns selfview on - /// - void SelfViewModeOn(); + /// + /// Turns selfview on + /// + void SelfViewModeOn(); - /// - /// Turns selfview off - /// - void SelfViewModeOff(); + /// + /// Turns selfview off + /// + void SelfViewModeOff(); - /// - /// Toggles selfview mode - /// - void SelfViewModeToggle(); -} + /// + /// Toggles selfview mode + /// + void SelfViewModeToggle(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingInfo.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingInfo.cs index ef0c72ed..6ec1f8da 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingInfo.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingInfo.cs @@ -2,21 +2,21 @@ using System; -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - - -/// -/// Describes a device that provides meeting information (like a ZoomRoom) -/// -public interface IHasMeetingInfo +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// - /// Raised when meeting info changes + /// Describes a device that provides meeting information (like a ZoomRoom) /// - event EventHandler MeetingInfoChanged; + public interface IHasMeetingInfo + { + /// + /// Raised when meeting info changes + /// + event EventHandler MeetingInfoChanged; - /// - /// Gets the current meeting information - /// - MeetingInfo MeetingInfo { get; } -} + /// + /// Gets the current meeting information + /// + MeetingInfo MeetingInfo { get; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingLock.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingLock.cs index de4dbdd5..b133eebd 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingLock.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingLock.cs @@ -1,29 +1,30 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -/// -/// Defines the contract for IHasMeetingLock -/// -public interface IHasMeetingLock +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// - /// Feedback that indicates whether the meeting is locked + /// Defines the contract for IHasMeetingLock /// - BoolFeedback MeetingIsLockedFeedback { get; } + public interface IHasMeetingLock + { + /// + /// Feedback that indicates whether the meeting is locked + /// + BoolFeedback MeetingIsLockedFeedback { get; } - /// - /// Locks the meeting - /// - void LockMeeting(); + /// + /// Locks the meeting + /// + void LockMeeting(); - /// - /// Unlocks the meeting - /// - void UnLockMeeting(); + /// + /// Unlocks the meeting + /// + void UnLockMeeting(); - /// - /// Toggles the meeting lock state - /// - void ToggleMeetingLock(); -} + /// + /// Toggles the meeting lock state + /// + void ToggleMeetingLock(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingRecording.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingRecording.cs index 6ec4c436..96e23daa 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingRecording.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasMeetingRecording.cs @@ -1,30 +1,30 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - - -/// -/// Defines the contract for IHasMeetingRecording -/// -public interface IHasMeetingRecording +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// - /// Feedback that indicates whether the meeting is being recorded + /// Defines the contract for IHasMeetingRecording /// - BoolFeedback MeetingIsRecordingFeedback { get; } + public interface IHasMeetingRecording + { + /// + /// Feedback that indicates whether the meeting is being recorded + /// + BoolFeedback MeetingIsRecordingFeedback { get; } - /// - /// Starts recording the meeting - /// - void StartRecording(); + /// + /// Starts recording the meeting + /// + void StartRecording(); - /// - /// Stops recording the meeting - /// - void StopRecording(); + /// + /// Stops recording the meeting + /// + void StopRecording(); - /// - /// Toggles recording the meeting - /// - void ToggleRecording(); -} + /// + /// Toggles recording the meeting + /// + void ToggleRecording(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasParticipants.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasParticipants.cs index 42869ee9..6ff549af 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasParticipants.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasParticipants.cs @@ -28,4 +28,4 @@ /// void AdmitParticipantFromWaitingRoom(int userId); } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs index c81152be..b2180c39 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasPresentationOnlyMeeting.cs @@ -1,57 +1,58 @@ -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -/// -/// Defines the contract for IHasPresentationOnlyMeeting -/// -public interface IHasPresentationOnlyMeeting +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// - /// Starts a presentation only meeting + /// Defines the contract for IHasPresentationOnlyMeeting /// - void StartSharingOnlyMeeting(); + public interface IHasPresentationOnlyMeeting + { + /// + /// Starts a presentation only meeting + /// + void StartSharingOnlyMeeting(); + + /// + /// Starts a presentation only meeting with specified display mode + /// + /// The display mode for the meeting + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode); + + /// + /// Starts a presentation only meeting with specified display mode and duration + /// + /// The display mode for the meeting + /// The duration for the meeting + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration); + + /// + /// Starts a presentation only meeting with specified display mode, duration, and password + /// + /// The display mode for the meeting + /// The duration for the meeting + /// The password for the meeting + void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration, string password); + + /// + /// Starts a normal meeting from a sharing only meeting + /// + void StartNormalMeetingFromSharingOnlyMeeting(); + } /// - /// Starts a presentation only meeting with specified display mode + /// Enumeration of eSharingMeetingMode values /// - /// The display mode for the meeting - void StartSharingOnlyMeeting(eSharingMeetingMode displayMode); - - /// - /// Starts a presentation only meeting with specified display mode and duration - /// - /// The display mode for the meeting - /// The duration for the meeting - void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration); - - /// - /// Starts a presentation only meeting with specified display mode, duration, and password - /// - /// The display mode for the meeting - /// The duration for the meeting - /// The password for the meeting - void StartSharingOnlyMeeting(eSharingMeetingMode displayMode, uint duration, string password); - - /// - /// Starts a normal meeting from a sharing only meeting - /// - void StartNormalMeetingFromSharingOnlyMeeting(); -} - -/// -/// Enumeration of eSharingMeetingMode values -/// -public enum eSharingMeetingMode -{ - /// - /// No specific sharing mode - /// - None, - /// - /// Laptop sharing mode - /// - Laptop, - /// - /// iOS sharing mode - /// - Ios, -} + public enum eSharingMeetingMode + { + /// + /// No specific sharing mode + /// + None, + /// + /// Laptop sharing mode + /// + Laptop, + /// + /// iOS sharing mode + /// + Ios, + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs index 3e9b861c..46c3d8f7 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewPosition.cs @@ -1,25 +1,25 @@ using PepperDash.Essentials.Devices.Common.VideoCodec; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - - -/// -/// Defines the contract for IHasSelfviewPosition -/// -public interface IHasSelfviewPosition +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets the SelfviewPipPositionFeedback + /// Defines the contract for IHasSelfviewPosition /// - StringFeedback SelfviewPipPositionFeedback { get; } + public interface IHasSelfviewPosition + { + /// + /// Gets the SelfviewPipPositionFeedback + /// + StringFeedback SelfviewPipPositionFeedback { get; } - /// - /// Sets the selfview position - /// - void SelfviewPipPositionSet(CodecCommandWithLabel position); + /// + /// Sets the selfview position + /// + void SelfviewPipPositionSet(CodecCommandWithLabel position); - /// - /// Toggles the selfview position - /// - void SelfviewPipPositionToggle(); -} + /// + /// Toggles the selfview position + /// + void SelfviewPipPositionToggle(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewSize.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewSize.cs index 505dca64..ab08a414 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewSize.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasSelfviewSize.cs @@ -1,25 +1,26 @@ using PepperDash.Essentials.Devices.Common.VideoCodec; -namespace PepperDash.Essentials.Core.DeviceTypeInterfaces; - -/// -/// Defines the contract for IHasSelfviewSize -/// -public interface IHasSelfviewSize +namespace PepperDash.Essentials.Core.DeviceTypeInterfaces { /// - /// Gets the SelfviewPipSizeFeedback + /// Defines the contract for IHasSelfviewSize /// - StringFeedback SelfviewPipSizeFeedback { get; } + public interface IHasSelfviewSize + { + /// + /// Gets the SelfviewPipSizeFeedback + /// + StringFeedback SelfviewPipSizeFeedback { get; } - /// - /// Sets the selfview size - /// - /// The new selfview size - void SelfviewPipSizeSet(CodecCommandWithLabel size); + /// + /// Sets the selfview size + /// + /// The new selfview size + void SelfviewPipSizeSet(CodecCommandWithLabel size); - /// - /// Toggles the selfview size - /// - void SelfviewPipSizeToggle(); + /// + /// Toggles the selfview size + /// + void SelfviewPipSizeToggle(); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStandbyMode.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStandbyMode.cs index 58247ae7..d0f1333b 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStandbyMode.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStandbyMode.cs @@ -1,46 +1,46 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Describes a device that has Standby Mode capability -/// -public interface IHasStandbyMode +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Feedback that indicates whether Standby Mode is on + /// Describes a device that has Standby Mode capability /// - BoolFeedback StandbyIsOnFeedback { get; } + public interface IHasStandbyMode + { + /// + /// Feedback that indicates whether Standby Mode is on + /// + BoolFeedback StandbyIsOnFeedback { get; } + + /// + /// Activates Standby Mode + /// + void StandbyActivate(); + + /// + /// Deactivates Standby Mode + /// + void StandbyDeactivate(); + } /// - /// Activates Standby Mode + /// Defines the contract for IHasHalfWakeMode /// - void StandbyActivate(); + public interface IHasHalfWakeMode : IHasStandbyMode + { + /// + /// Feedback that indicates whether Half Wake Mode is on + /// + BoolFeedback HalfWakeModeIsOnFeedback { get; } - /// - /// Deactivates Standby Mode - /// - void StandbyDeactivate(); -} + /// + /// Feedback that indicates whether the device is entering Standby Mode + /// + BoolFeedback EnteringStandbyModeFeedback { get; } -/// -/// Defines the contract for IHasHalfWakeMode -/// -public interface IHasHalfWakeMode : IHasStandbyMode -{ - /// - /// Feedback that indicates whether Half Wake Mode is on - /// - BoolFeedback HalfWakeModeIsOnFeedback { get; } - - /// - /// Feedback that indicates whether the device is entering Standby Mode - /// - BoolFeedback EnteringStandbyModeFeedback { get; } - - /// - /// Activates Half Wake Mode - /// - void HalfwakeActivate(); -} + /// + /// Activates Half Wake Mode + /// + void HalfwakeActivate(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStartMeeting.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStartMeeting.cs index 2e2f0266..357416e2 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStartMeeting.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasStartMeeting.cs @@ -1,23 +1,24 @@ -namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -/// -/// Describes the ability to start an ad-hoc meeting -/// -public interface IHasStartMeeting +namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// - /// The default meeting duration in minutes + /// Describes the ability to start an ad-hoc meeting /// - uint DefaultMeetingDurationMin { get; } + public interface IHasStartMeeting + { + /// + /// The default meeting duration in minutes + /// + uint DefaultMeetingDurationMin { get; } - /// - /// Start an ad-hoc meeting for the specified duration - /// - /// - void StartMeeting(uint duration); + /// + /// Start an ad-hoc meeting for the specified duration + /// + /// + void StartMeeting(uint duration); - /// - /// Leaves a meeting without ending it - /// - void LeaveMeeting(); + /// + /// Leaves a meeting without ending it + /// + void LeaveMeeting(); + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasVideoCodec.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasVideoCodec.cs index 91d2fba9..cdeb9190 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasVideoCodec.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IHasVideoCodec.cs @@ -1,25 +1,26 @@ using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - -/// -/// For rooms that have video codec -/// -public interface IHasVideoCodec : IHasInCallFeedback, IPrivacy +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Gets the VideoCodecBase instance + /// For rooms that have video codec /// - VideoCodecBase VideoCodec { get; } + public interface IHasVideoCodec : IHasInCallFeedback, IPrivacy + { + /// + /// Gets the VideoCodecBase instance + /// + VideoCodecBase VideoCodec { get; } - /// - /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis - /// - IntFeedback CallTypeFeedback { get; } + /// + /// States: 0 for on hook, 1 for video, 2 for audio, 3 for telekenesis + /// + IntFeedback CallTypeFeedback { get; } - /// - /// When something in the room is sharing with the far end or through other means - /// - BoolFeedback IsSharingFeedback { get; } + /// + /// When something in the room is sharing with the far end or through other means + /// + BoolFeedback IsSharingFeedback { get; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IJoinCalls.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IJoinCalls.cs index bf0fe51c..b2583a5d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IJoinCalls.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/IJoinCalls.cs @@ -1,21 +1,21 @@ using PepperDash.Essentials.Devices.Common.Codec; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Defines the contract for IJoinCalls -/// -public interface IJoinCalls +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Joins a call + /// Defines the contract for IJoinCalls /// - /// The active call to join - void JoinCall(CodecActiveCallItem activeCall); + public interface IJoinCalls + { + /// + /// Joins a call + /// + /// The active call to join + void JoinCall(CodecActiveCallItem activeCall); - /// - /// Joins all calls - /// - void JoinAllCalls(); -} + /// + /// Joins all calls + /// + void JoinAllCalls(); + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/Participant.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/Participant.cs index 5dfd18a0..2d266a95 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/Participant.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/Participant.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json; namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces { /// @@ -9,79 +8,56 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces /// /// Gets or sets the UserId /// - [JsonProperty("userId")] public int UserId { get; set; } /// /// Gets or sets the IsHost /// - [JsonProperty("isHost")] public bool IsHost { get; set; } - /// - /// Gets or sets the IsCohost - /// - [JsonProperty("isCohost")] - public bool IsCohost { get; set; } - /// /// Gets or sets the IsMyself /// - [JsonProperty("isMyself")] public bool IsMyself { get; set; } /// /// Gets or sets the Name /// - [JsonProperty("name")] public string Name { get; set; } /// /// Gets or sets the Email /// - [JsonProperty("email")] public bool CanMuteVideo { get; set; } /// /// Gets or sets the CanUnmuteVideo /// - [JsonProperty("canUnmuteVideo")] public bool CanUnmuteVideo { get; set; } /// /// Gets or sets the CanMuteAudio /// - [JsonProperty("canMuteAudio")] - public bool CanMuteAudio { get; set; } + public bool VideoMuteFb { get; set; } /// /// Gets or sets the AudioMuteFb /// - [JsonProperty("audioMuteFb")] public bool AudioMuteFb { get; set; } - /// - /// Gets or sets the VideoMuteFb - /// - [JsonProperty("videoMuteFb")] - public bool VideoMuteFb { get; set; } - /// /// Gets or sets the HandIsRaisedFb /// - [JsonProperty("handIsRaisedFb")] public bool HandIsRaisedFb { get; set; } /// /// Gets or sets the IsPinnedFb /// - [JsonProperty("isPinnedFb")] public bool IsPinnedFb { get; set; } /// /// Gets or sets the ScreenIndexIsPinnedToFb /// - [JsonProperty("screenIndexIsPinnedToFb")] public int ScreenIndexIsPinnedToFb { get; set; } /// diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs index a2dd9e55..8b1be9e9 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/Interfaces/iVideoCodecInfo.cs @@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Devices.Common.Codec /// /// Implements a common set of data about a codec /// - public interface IVideoCodecInfo + public interface iVideoCodecInfo { /// /// Gets the codec information diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockCodecDirectory.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockCodecDirectory.cs index 2e2f2653..185f089f 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockCodecDirectory.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockCodecDirectory.cs @@ -107,7 +107,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec { new DirectoryContact() { - Name = "Overflow", + Name = "Corporate Bridge", ContactMethods = new List() { new ContactMethod() @@ -217,14 +217,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "nyc_2", - Name = "Priya Chandran", + Name = "Sumanth Rayancha", Title = @"CTO", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "pchandran.pepperdash.com", + Number = "srayancha.pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } @@ -233,14 +233,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "nyc_3", - Name = "Miles Anderson", + Name = "Justin Gordon", Title = @"Software Developer", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "manderson.pepperdash.com", + Number = "jgordon.pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } @@ -309,14 +309,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "sfo_1", - Name = "Owen Bradshaw", + Name = "David Huselid", Title = @"Cive President, COO", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "obradshaw.pepperdash.com", + Number = "dhuselid.pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } @@ -347,14 +347,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "den_1", - Name = "Derek Simmons", + Name = "Heath Volmer", Title = @"Software Developer", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "dsimmons.pepperdash.com", + Number = "hvolmer.pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } @@ -385,14 +385,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "atx_1", - Name = "Marcus Webb", + Name = "Vincent Longano", Title = @"Product Development Manager", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "mwebb.pepperdash.com", + Number = "vlongano.pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } @@ -452,14 +452,14 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec new DirectoryContact() { ContactId = "cdn_1", - Name = "Alex Turner", + Name = "Neil Dorin", Title = @"Software Developer /SC", ContactMethods = new List() { new ContactMethod() { ContactMethodId = "cid_1", - Number = "aturner@pepperdash.com", + Number = "ndorin@pepperdash.com", Device = eContactMethodDevice.Video, CallType = eContactMethodCallType.Video } diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs index c3015a83..bdefa09d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVC.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Timers; +using Crestron.SimplSharp; using Crestron.SimplSharpPro.DeviceSupport; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -14,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.Devices.Common.VideoCodec.Interfaces; using Serilog.Events; namespace PepperDash.Essentials.Devices.Common.VideoCodec @@ -22,8 +21,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// /// Represents a MockVC /// - public class MockVC : VideoCodecBase, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, - IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IRoutingSinkWithFeedback, IHasParticipants + public class MockVC : VideoCodecBase, IRoutingSource, IHasCallHistory, IHasScheduleAwareness, IHasCallFavorites, IHasDirectory, IHasCodecCameras, IHasCameraAutoMode, IHasCodecRoomPresets, IRoutingInputs { /// /// Gets or sets the PropertiesConfig @@ -42,44 +40,18 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// Gets or sets the HdmiIn2 /// public RoutingInputPort HdmiIn2 { get; private set; } - - /// - /// Gets or sets the first USB camera input (usbIn1), matching the port key used by the - /// real Zoom Room plugin for simultaneous multi-camera routing. - /// - public RoutingInputPort UsbIn1 { get; private set; } - /// - /// Gets or sets the second USB camera input (usbIn2). - /// - public RoutingInputPort UsbIn2 { get; private set; } - /// - /// Gets or sets the third USB camera input (usbIn3). - /// - public RoutingInputPort UsbIn3 { get; private set; } - /// - /// Gets or sets the fourth USB camera input (usbIn4). - /// - public RoutingInputPort UsbIn4 { get; private set; } - /// /// Gets or sets the HdmiOut /// - public RoutingOutputPort HdmiOut1 { get; private set; } - - /// - /// Gets or sets the HdmiOut2 - /// - public RoutingOutputPort HdmiOut2 { get; private set; } + public RoutingOutputPort HdmiOut { get; private set; } /// /// Gets or sets the CallFavorites /// public CodecCallFavorites CallFavorites { get; private set; } - private readonly CodecParticipants _participants; - /// - /// Initializes a new instance of the class. + /// /// public MockVC(DeviceConfig config) : base(config) @@ -97,23 +69,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec DirectoryBrowseHistory = new List(); - _participants = new CodecParticipants(); - - // Populate a fake participants list whenever a call connects, and clear it once all - // calls have ended, so IHasParticipants consumers (e.g. the roster UI) have something - // to display without needing a real far-end connection. - CallStatusChange += (o, a) => - { - if (a.CallItem.Status == eCodecCallStatus.Connected) - { - PopulateFakeParticipants(); - } - else if (a.CallItem.Status == eCodecCallStatus.Disconnected && ActiveCalls.Count == 0) - { - Participants.CurrentParticipants = new List(); - } - }; - // Debug helpers MuteFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "Mute={0}", _IsMuted); PrivacyModeIsOnFeedback.OutputChange += (o, a) => Debug.LogMessage(LogEventLevel.Debug, this, "Privacy={0}", _PrivacyModeIsOn); @@ -130,22 +85,8 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec InputPorts.Add(HdmiIn1); HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, 2, this); InputPorts.Add(HdmiIn2); - // USB camera inputs - each participant's camera routes to one of these independently - // (unlike HdmiIn1/2, which represent a single selectable content source). Port keys - // match the real Zoom Room plugin ("usbIn1".."usbIn4") so demo/example configs that - // reference them resolve to an actual port instead of silently failing to find one. - UsbIn1 = new RoutingInputPort("usbIn1", eRoutingSignalType.Video, eRoutingPortConnectionType.UsbC, 3, this); - InputPorts.Add(UsbIn1); - UsbIn2 = new RoutingInputPort("usbIn2", eRoutingSignalType.Video, eRoutingPortConnectionType.UsbC, 4, this); - InputPorts.Add(UsbIn2); - UsbIn3 = new RoutingInputPort("usbIn3", eRoutingSignalType.Video, eRoutingPortConnectionType.UsbC, 5, this); - InputPorts.Add(UsbIn3); - UsbIn4 = new RoutingInputPort("usbIn4", eRoutingSignalType.Video, eRoutingPortConnectionType.UsbC, 6, this); - InputPorts.Add(UsbIn4); - HdmiOut1 = new RoutingOutputPort(RoutingPortNames.HdmiOut1, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); - OutputPorts.Add(HdmiOut1); - HdmiOut2 = new RoutingOutputPort(RoutingPortNames.HdmiOut2, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); - OutputPorts.Add(HdmiOut2); + HdmiOut = new RoutingOutputPort(RoutingPortNames.HdmiOut, eRoutingSignalType.Audio | eRoutingSignalType.Video, eRoutingPortConnectionType.Hdmi, null, this); + OutputPorts.Add(HdmiOut); CallHistory = new CodecCallHistory(); for (int i = 0; i < 10; i++) @@ -229,16 +170,12 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec OnCallStatusChange(call); //ActiveCallCountFeedback.FireUpdate(); // Simulate 2-second ring, then connecting, then connected - var dialTimer = new Timer(2000) { AutoReset = false }; - dialTimer.Elapsed += (s, e) => + new CTimer(o => { call.Type = eCodecCallType.Video; SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); - var connectTimer = new Timer(1000) { AutoReset = false }; - connectTimer.Elapsed += (ss, ee) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call); - connectTimer.Start(); - }; - dialTimer.Start(); + new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); + }, 2000); } /// @@ -251,40 +188,15 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec //ActiveCallCountFeedback.FireUpdate(); // Simulate 2-second ring, then connecting, then connected - var dialMeetingTimer = new Timer(2000) { AutoReset = false }; - dialMeetingTimer.Elapsed += (s, e) => + new CTimer(o => { call.Type = eCodecCallType.Video; SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); - var connectTimer = new Timer(1000) { AutoReset = false }; - connectTimer.Elapsed += (ss, ee) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call); - connectTimer.Start(); - }; - dialMeetingTimer.Start(); + new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); + }, 2000); } - /// - public override void Dial(string number, string password) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Dial: {0} with password: {1}", number, password); - var call = new CodecActiveCallItem() { Name = number, Number = number, Id = number, Status = eCodecCallStatus.Dialing, Direction = eCodecCallDirection.Outgoing, Type = eCodecCallType.Video }; - ActiveCalls.Add(call); - OnCallStatusChange(call); - //ActiveCallCountFeedback.FireUpdate(); - // Simulate 2-second ring, then connecting, then connected - var dialTimer = new Timer(2000) { AutoReset = false }; - dialTimer.Elapsed += (s, e) => - { - call.Type = eCodecCallType.Video; - SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); - var connectTimer = new Timer(1000) { AutoReset = false }; - connectTimer.Elapsed += (ss, ee) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call); - connectTimer.Start(); - }; - dialTimer.Start(); - } - /// public override void EndCall(CodecActiveCallItem call) { @@ -314,9 +226,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec { Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall"); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); - var acceptTimer = new Timer(1000) { AutoReset = false }; - acceptTimer.Elapsed += (s, e) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call); - acceptTimer.Start(); + new CTimer(o => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); // should already be in active list } @@ -708,72 +618,13 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec #endregion - #region IHasParticipants Members - - /// - /// Gets the collection of call participants - /// - public CodecParticipants Participants - { - get { return _participants; } - } - - /// - /// Builds a fake set of participants and assigns it to , - /// simulating a codec that has joined a call with other people already present. - /// - private void PopulateFakeParticipants() - { - Participants.CurrentParticipants = new List - { - new Participant { UserId = 1, Name = "Myself", IsHost = true, IsMyself = true, CanMuteAudio = true, CanUnmuteVideo = true }, - new Participant { UserId = 2, Name = "Priya Chandran", CanMuteAudio = true, CanUnmuteVideo = true }, - new Participant { UserId = 3, Name = "Miles Anderson", CanMuteAudio = true, CanUnmuteVideo = true, AudioMuteFb = true }, - new Participant { UserId = 4, Name = "Owen Bradshaw", CanMuteAudio = true, CanUnmuteVideo = true, HandIsRaisedFb = true }, - }; - } - - /// - public void RemoveParticipant(int userId) - { - Debug.LogMessage(LogEventLevel.Debug, this, "RemoveParticipant: {0}", userId); - - var remaining = Participants.CurrentParticipants.Where(p => p.UserId != userId).ToList(); - - Participants.CurrentParticipants = remaining; - } - - /// - public void SetParticipantAsHost(int userId) - { - Debug.LogMessage(LogEventLevel.Debug, this, "SetParticipantAsHost: {0}", userId); - - foreach (var p in Participants.CurrentParticipants) - { - p.IsHost = p.UserId == userId; - } - - // Re-assign to trigger ParticipantsListHasChanged, since the mutations above happened - // on the existing list/items in place. - Participants.CurrentParticipants = Participants.CurrentParticipants; - } - - /// - public void AdmitParticipantFromWaitingRoom(int userId) - { - // No waiting room is simulated for this mock codec. - Debug.LogMessage(LogEventLevel.Debug, this, "AdmitParticipantFromWaitingRoom: {0} (no-op, mock has no waiting room)", userId); - } - - #endregion - void SetupCameras() { SupportsCameraAutoMode = true; SupportsCameraOff = false; - Cameras = new List(); + Cameras = new List(); var internalCamera = new MockVCCamera(Key + "-camera1", "Near End", this); @@ -828,19 +679,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec /// /// CameraSelected event. Fired when a camera is selected /// - public event EventHandler> CameraSelected; + public event EventHandler CameraSelected; /// /// Gets the list of cameras associated with this codec /// - public List Cameras { get; private set; } + public List Cameras { get; private set; } - private IHasCameraControls _selectedCamera; + private CameraBase _selectedCamera; /// /// Returns the selected camera /// - public IHasCameraControls SelectedCamera + public CameraBase SelectedCamera { get { @@ -851,7 +702,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec _selectedCamera = value; SelectedCameraFeedback.FireUpdate(); ControllingFarEndCameraFeedback.FireUpdate(); - CameraSelected?.Invoke(this, new CameraSelectedEventArgs(SelectedCamera)); + CameraSelected?.Invoke(this, new CameraSelectedEventArgs(SelectedCamera)); } } @@ -997,54 +848,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec #endregion - #region IRoutingSinkWithFeedback Members - - /// - /// Gets the currently routed input port for this sink - /// - public RoutingInputPort CurrentInputPort { get; private set; } - - /// - /// Event fired when the currently routed input changes - /// - public event InputChangedEventHandler InputChanged; - - #endregion - - #region ICurrentSources Members - - /// - /// Gets the currently routed source devices, keyed by signal type - /// - public Dictionary CurrentSources { get; private set; } = new Dictionary(); - - /// - /// Gets the keys of the currently routed source devices, keyed by signal type - /// - public Dictionary CurrentSourceKeys { get; private set; } = new Dictionary(); - - /// - /// Event fired when the currently routed source for a signal type changes - /// - public event EventHandler CurrentSourcesChanged; - - /// - /// Sets the currently routed source device for the given signal type, updating - /// and and firing - /// - /// - /// The signal type being routed - /// The source device now routed to this signal type, or null if cleared - public void SetCurrentSource(eRoutingSignalType signalType, IRoutingSource sourceDevice) - { - CurrentSources.TryGetValue(signalType, out var previousSource); - CurrentSources[signalType] = sourceDevice; - CurrentSourceKeys[signalType] = sourceDevice?.Key; - CurrentSourcesChanged?.Invoke(this, new CurrentSourcesChangedEventArgs(signalType, previousSource, sourceDevice)); - } - - #endregion - /// protected override void CustomSetConfig(DeviceConfig config) { diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVCCamera.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVCCamera.cs index d2fba918..5c7e89d8 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVCCamera.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVCCamera.cs @@ -4,198 +4,299 @@ using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Devices.Common.VideoCodec; using Serilog.Events; -namespace PepperDash.Essentials.Devices.Common.Cameras; - -public class MockVCCamera : CameraBase, IHasCameraPtzControl, IHasCameraFocusControl, IBridgeAdvanced +namespace PepperDash.Essentials.Devices.Common.Cameras { - protected VideoCodecBase ParentCodec { get; private set; } - - - public MockVCCamera(string key, string name, VideoCodecBase codec) - : base(key, name) + /// + /// Represents a MockVCCamera + /// + public class MockVCCamera : CameraBase, IHasCameraPtzControl, IHasCameraFocusControl, IBridgeAdvanced { - Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus; + /// + /// Gets the parent video codec + /// + protected VideoCodecBase ParentCodec { get; private set; } - ParentCodec = codec; + /// + /// Initializes a new instance of the MockVCCamera class + /// + /// The device key + /// The device name + /// The parent video codec + public MockVCCamera(string key, string name, VideoCodecBase codec) + : base(key, name) + { + Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus; + + ParentCodec = codec; + } + + #region IHasCameraPtzControl Members + + /// + /// PositionHome method + /// + public void PositionHome() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Resetting to home position"); + } + + #endregion + + #region IHasCameraPanControl Members + + /// + /// PanLeft method + /// + public void PanLeft() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Panning Left"); + } + + /// + /// PanRight method + /// + public void PanRight() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Panning Right"); + } + + /// + /// PanStop method + /// + public void PanStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Pan"); + } + + #endregion + + #region IHasCameraTiltControl Members + + /// + /// TiltDown method + /// + public void TiltDown() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Down"); + } + + /// + /// TiltUp method + /// + public void TiltUp() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Up"); + } + + /// + /// TiltStop method + /// + public void TiltStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Tilt"); + } + + #endregion + + #region IHasCameraZoomControl Members + + /// + /// ZoomIn method + /// + public void ZoomIn() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Zooming In"); + } + + /// + /// ZoomOut method + /// + public void ZoomOut() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Zooming Out"); + } + + /// + /// ZoomStop method + /// + public void ZoomStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Zoom"); + } + + #endregion + + #region IHasCameraFocusControl Members + + /// + /// FocusNear method + /// + public void FocusNear() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Focusing Near"); + } + + /// + /// FocusFar method + /// + public void FocusFar() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Focusing Far"); + } + + /// + /// FocusStop method + /// + public void FocusStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Focus"); + } + + /// + /// TriggerAutoFocus method + /// + public void TriggerAutoFocus() + { + Debug.LogMessage(LogEventLevel.Debug, this, "AutoFocus Triggered"); + } + + #endregion + + /// + /// LinkToApi method + /// + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge); + } } - #region IHasCameraPtzControl Members - - public void PositionHome() + /// + /// Represents a MockFarEndVCCamera + /// + public class MockFarEndVCCamera : CameraBase, IHasCameraPtzControl, IAmFarEndCamera, IBridgeAdvanced { - Debug.LogMessage(LogEventLevel.Debug, this, "Resetting to home position"); - } + /// + /// Gets the parent video codec + /// + protected VideoCodecBase ParentCodec { get; private set; } - #endregion + /// + /// Initializes a new instance of the MockFarEndVCCamera class + /// + /// The device key + /// The device name + /// The parent video codec + public MockFarEndVCCamera(string key, string name, VideoCodecBase codec) + : base(key, name) + { + Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom; - #region IHasCameraPanControl Members + ParentCodec = codec; + } - public void PanLeft() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Panning Left"); - } + #region IHasCameraPtzControl Members - public void PanRight() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Panning Right"); - } + /// + /// PositionHome method + /// + public void PositionHome() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Resetting to home position"); + } - public void PanStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Pan"); - } + #endregion - #endregion + #region IHasCameraPanControl Members - #region IHasCameraTiltControl Members + /// + /// PanLeft method + /// + public void PanLeft() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Panning Left"); + } - public void TiltDown() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Down"); - } + /// + /// PanRight method + /// + public void PanRight() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Panning Right"); + } - public void TiltUp() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Up"); - } + /// + /// PanStop method + /// + public void PanStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Pan"); + } - public void TiltStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Tilt"); - } + #endregion - #endregion + #region IHasCameraTiltControl Members - #region IHasCameraZoomControl Members + /// + /// TiltDown method + /// + public void TiltDown() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Down"); + } - public void ZoomIn() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Zooming In"); - } + /// + /// TiltUp method + /// + public void TiltUp() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Up"); + } - public void ZoomOut() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Zooming Out"); - } + /// + /// TiltStop method + /// + public void TiltStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Tilt"); + } - public void ZoomStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Zoom"); - } + #endregion - #endregion + #region IHasCameraZoomControl Members - #region IHasCameraFocusControl Members + /// + /// ZoomIn method + /// + public void ZoomIn() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Zooming In"); + } - public void FocusNear() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Focusing Near"); - } + /// + /// ZoomOut method + /// + public void ZoomOut() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Zooming Out"); + } - public void FocusFar() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Focusing Far"); - } + /// + /// ZoomStop method + /// + public void ZoomStop() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Zoom"); + } - public void FocusStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Focus"); - } + #endregion - public void TriggerAutoFocus() - { - Debug.LogMessage(LogEventLevel.Debug, this, "AutoFocus Triggered"); - } - - #endregion - - public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge); - } -} - -public class MockFarEndVCCamera : CameraBase, IHasCameraPtzControl, IAmFarEndCamera, IBridgeAdvanced -{ - protected VideoCodecBase ParentCodec { get; private set; } - - - public MockFarEndVCCamera(string key, string name, VideoCodecBase codec) - : base(key, name) - { - Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom; - - ParentCodec = codec; - } - - #region IHasCameraPtzControl Members - - public void PositionHome() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Resetting to home position"); - } - - #endregion - - #region IHasCameraPanControl Members - - public void PanLeft() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Panning Left"); - } - - public void PanRight() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Panning Right"); - } - - public void PanStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Pan"); - } - - #endregion - - #region IHasCameraTiltControl Members - - public void TiltDown() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Down"); - } - - public void TiltUp() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Tilting Up"); - } - - public void TiltStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Tilt"); - } - - #endregion - - #region IHasCameraZoomControl Members - - public void ZoomIn() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Zooming In"); - } - - public void ZoomOut() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Zooming Out"); - } - - public void ZoomStop() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Stopping Zoom"); - } - - #endregion - - public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) - { - LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge); + /// + /// LinkToApi method + /// + public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) + { + LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge); + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVcPropertiesConfig.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVcPropertiesConfig.cs index aa0bcadd..0c5654d6 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVcPropertiesConfig.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/MockVC/MockVcPropertiesConfig.cs @@ -2,32 +2,32 @@ using Newtonsoft.Json; using PepperDash.Essentials.Devices.Common.Codec; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - - -/// -/// Represents a MockVcPropertiesConfig -/// -public class MockVcPropertiesConfig +namespace PepperDash.Essentials.Devices.Common.VideoCodec { /// - /// Gets or sets the Favorites + /// Represents a MockVcPropertiesConfig /// - [JsonProperty("favorites")] - public List Favorites { get; set; } - - /// - /// Gets or sets the Presets - /// - [JsonProperty("presets")] - public List Presets { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public MockVcPropertiesConfig() + public class MockVcPropertiesConfig { - Favorites = new List(); - Presets = new List(); + /// + /// Gets or sets the Favorites + /// + [JsonProperty("favorites")] + public List Favorites { get; set; } + + /// + /// Gets or sets the Presets + /// + [JsonProperty("presets")] + public List Presets { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public MockVcPropertiesConfig() + { + Favorites = new List(); + Presets = new List(); + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs index 0d776703..04c7e64d 100644 --- a/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs +++ b/src/PepperDash.Essentials.Devices.Common/VideoCodec/VideoCodecBase.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro.DeviceSupport; using PepperDash.Core; @@ -18,663 +20,675 @@ using PepperDash.Essentials.Devices.Common.Codec; using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; using Serilog.Events; using Feedback = PepperDash.Essentials.Core.Feedback; -using System.Text; -using System.Threading.Tasks; -namespace PepperDash.Essentials.Devices.Common.VideoCodec; - -/// -/// Base class for video codecs. Contains common properties, methods, and feedback for video codecs. -/// Also contains the logic to link commonly implemented interfaces to the API bridge. -/// -public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingSource, - IUsageTracking, ICodecCallControls, IHasContentSharing, ICodecAudio, IVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode, IHasReady +namespace PepperDash.Essentials.Devices.Common.VideoCodec { - private const int XSigEncoding = 28591; - /// - /// Maximum number of participants to display in the participant list. - /// This is used to limit the number of XSigs that need to be updated and processed on the control system when there are many participants in a call. + /// Base class for video codec devices /// - protected const int MaxParticipants = 50; - private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs(); - - private readonly IHasDirectory _directoryCodec; - private readonly BasicTriList _directoryTrilist; - private readonly VideoCodecControllerJoinMap _directoryJoinmap; - - /// - /// Time format specifier for any time-related XSigs. - /// This should be set by the inheriting class based on the expected format of the codec (e.g. "hh:mm:ss" or "mm:ss") - /// - protected string _timeFormatSpecifier; - - /// - /// Date format specifier - /// - protected string _dateFormatSpecifier; - - /// - /// Initializes a new instance of the class. - /// - protected VideoCodecBase(DeviceConfig config) - : base(config) + public abstract class VideoCodecBase : ReconfigurableDevice, + IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode { + private const int XSigEncoding = 28591; - StandbyIsOnFeedback = new BoolFeedback("standbyIsOn", StandbyIsOnFeedbackFunc); - PrivacyModeIsOnFeedback = new BoolFeedback("privacyModeIsOn", PrivacyModeIsOnFeedbackFunc); - VolumeLevelFeedback = new IntFeedback("volumeLevel", VolumeLevelFeedbackFunc); - MuteFeedback = new BoolFeedback("mute", MuteFeedbackFunc); - SharingSourceFeedback = new StringFeedback("sharingSource", SharingSourceFeedbackFunc); - SharingContentIsOnFeedback = new BoolFeedback("sharingContentIsOn", SharingContentIsOnFeedbackFunc); + /// + /// Maximum number of participants + /// + protected const int MaxParticipants = 50; + private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs(); - MeetingsToDisplayFeedback = new IntFeedback(() => MeetingsToDisplay); + /// + /// Time format specifier + /// + protected string _timeFormatSpecifier; - InputPorts = new RoutingPortCollection(); - OutputPorts = new RoutingPortCollection(); + /// + /// Date format specifier + /// + protected string _dateFormatSpecifier; - ActiveCalls = new List(); - } - - /// - /// Gets or sets the Communication - /// - public IBasicCommunication Communication { get; protected set; } - - /// - /// Gets or sets the OsdSource - /// - public DummyRoutingInputsDevice OsdSource { get; protected set; } - - /// - /// Gets or sets the StandbyIsOnFeedback - /// - public BoolFeedback StandbyIsOnFeedback { get; private set; } - - /// - /// Gets or sets the PrivacyModeIsOnFeedbackFunc - /// - protected abstract Func PrivacyModeIsOnFeedbackFunc { get; } - - /// - /// Gets or sets the VolumeLevelFeedbackFunc - /// - protected abstract Func VolumeLevelFeedbackFunc { get; } - - /// - /// Gets or sets the MuteFeedbackFunc - /// - protected abstract Func MuteFeedbackFunc { get; } - - /// - /// Gets or sets the StandbyIsOnFeedbackFunc - /// - protected abstract Func StandbyIsOnFeedbackFunc { get; } - - /// - /// Gets or sets the ActiveCalls - /// - public List ActiveCalls { get; set; } - - /// - /// Gets or sets the ShowSelfViewByDefault - /// - public bool ShowSelfViewByDefault { get; protected set; } - - /// - public bool SupportsCameraOff { get; protected set; } - - /// - public bool SupportsCameraAutoMode { get; protected set; } - - /// - /// Gets or sets the IsReady - /// - public bool IsReady { get; protected set; } - - /// - public virtual List Feedbacks - { - get + /// + /// Initializes a new instance of the class. + /// + protected VideoCodecBase(DeviceConfig config) + : base(config) { - return new List + + StandbyIsOnFeedback = new BoolFeedback("standbyIsOn", StandbyIsOnFeedbackFunc); + PrivacyModeIsOnFeedback = new BoolFeedback("privacyModeIsOn", PrivacyModeIsOnFeedbackFunc); + VolumeLevelFeedback = new IntFeedback("volumeLevel", VolumeLevelFeedbackFunc); + MuteFeedback = new BoolFeedback("mute", MuteFeedbackFunc); + SharingSourceFeedback = new StringFeedback("sharingSource", SharingSourceFeedbackFunc); + SharingContentIsOnFeedback = new BoolFeedback("sharingContentIsOn", SharingContentIsOnFeedbackFunc); + + // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set + MeetingsToDisplayFeedback = new IntFeedback("meetingsToDisplay", () => MeetingsToDisplay); + + InputPorts = new RoutingPortCollection(); + OutputPorts = new RoutingPortCollection(); + + ActiveCalls = new List(); + } + + /// + /// Gets or sets the Communication + /// + public IBasicCommunication Communication { get; protected set; } + + /// + /// Gets or sets the OsdSource + /// + public DummyRoutingInputsDevice OsdSource { get; protected set; } + + /// + /// Gets or sets the StandbyIsOnFeedback + /// + public BoolFeedback StandbyIsOnFeedback { get; private set; } + + /// + /// Gets or sets the PrivacyModeIsOnFeedbackFunc + /// + protected abstract Func PrivacyModeIsOnFeedbackFunc { get; } + + /// + /// Gets or sets the VolumeLevelFeedbackFunc + /// + protected abstract Func VolumeLevelFeedbackFunc { get; } + + /// + /// Gets or sets the MuteFeedbackFunc + /// + protected abstract Func MuteFeedbackFunc { get; } + + /// + /// Gets or sets the StandbyIsOnFeedbackFunc + /// + protected abstract Func StandbyIsOnFeedbackFunc { get; } + + /// + /// Gets or sets the ActiveCalls + /// + public List ActiveCalls { get; set; } + + /// + /// Gets or sets the ShowSelfViewByDefault + /// + public bool ShowSelfViewByDefault { get; protected set; } + + /// + /// Gets or sets the SupportsCameraOff + /// + public bool SupportsCameraOff { get; protected set; } + /// + /// Gets or sets the SupportsCameraAutoMode + /// + public bool SupportsCameraAutoMode { get; protected set; } + + /// + /// Gets or sets the IsReady + /// + public bool IsReady { get; protected set; } + + /// + public virtual List Feedbacks + { + get { - PrivacyModeIsOnFeedback, - SharingSourceFeedback + return new List + { + PrivacyModeIsOnFeedback, + SharingSourceFeedback + }; + } + } + + /// + /// Gets or sets the MeetingsToDisplay + /// + protected abstract Func SharingSourceFeedbackFunc { get; } + + /// + /// Gets or sets the SharingContentIsOnFeedbackFunc + /// + protected abstract Func SharingContentIsOnFeedbackFunc { get; } + + #region ICodecAudio Members + + /// + /// Set Privacy Mode On + /// + public abstract void PrivacyModeOn(); + + /// + /// Set Privacy Mode Off + /// + public abstract void PrivacyModeOff(); + + /// + /// Toggles the Privacy Mode + /// + public abstract void PrivacyModeToggle(); + + /// + /// Gets or sets the PrivacyModeIsOnFeedback + /// + public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } + + /// + /// Gets or sets the MuteFeedback + /// + public BoolFeedback MuteFeedback { get; private set; } + + /// + /// Sets the Mute state to Off + /// + public abstract void MuteOff(); + + /// + /// Sets the Mute state to On + /// + public abstract void MuteOn(); + + /// + /// Sets the Volume level + /// + public abstract void SetVolume(ushort level); + + /// + /// Gets or sets the VolumeLevelFeedback + /// + public IntFeedback VolumeLevelFeedback { get; private set; } + + /// + /// Toggles the Mute state + /// + public abstract void MuteToggle(); + + /// + /// Decreases the Volume level + /// + public abstract void VolumeDown(bool pressRelease); + + /// + /// Increases the Volume level + /// + public abstract void VolumeUp(bool pressRelease); + + #endregion + + #region IHasContentSharing Members + + /// + /// Starts content sharing + /// + public abstract void StartSharing(); + + /// + /// Stops content sharing + /// + public abstract void StopSharing(); + + /// + /// Gets or sets the AutoShareContentWhileInCall + /// + public bool AutoShareContentWhileInCall { get; protected set; } + + /// + /// Gets or sets the SharingSourceFeedback + /// + public StringFeedback SharingSourceFeedback { get; private set; } + + /// + /// Gets or sets the SharingContentIsOnFeedback + /// + public BoolFeedback SharingContentIsOnFeedback { get; private set; } + + #endregion + + #region IHasDialer Members + + /// + /// Fires when the status of any active, dialing, or incoming call changes or is new + /// + public event EventHandler CallStatusChange; + + /// + /// Returns true when any call is not in state Unknown, Disconnecting, Disconnected + /// + public bool IsInCall + { + get + { + var value = ActiveCalls != null && ActiveCalls.Any(c => c.IsActiveCall); + return value; + } + } + + /// + /// Dials the specified number + /// + public abstract void Dial(string number); + + /// + /// Ends the specified call + /// + public abstract void EndCall(CodecActiveCallItem call); + + /// + /// Ends all active calls + /// + public abstract void EndAllCalls(); + + /// + /// Accepts the specified call + /// + public abstract void AcceptCall(CodecActiveCallItem call); + + /// + /// Rejects the specified call + /// + public abstract void RejectCall(CodecActiveCallItem call); + + /// + /// Sends DTMF tones + /// + public abstract void SendDtmf(string s); + + /// + /// SendDtmf method + /// + public virtual void SendDtmf(string s, CodecActiveCallItem call) { } + + #endregion + + #region IRoutingInputsOutputs Members + + /// + /// Gets or sets the InputPorts + /// + public RoutingPortCollection InputPorts { get; private set; } + + /// + /// Gets or sets the OutputPorts + /// + public RoutingPortCollection OutputPorts { get; private set; } + + #endregion + + #region IUsageTracking Members + + /// + /// Gets or sets the UsageTracker + /// + public UsageTracking UsageTracker { get; set; } + + #endregion + + #region iVideoCodecInfo Members + + /// + /// Gets the CodecInfo + /// + public VideoCodecInfo CodecInfo { get; protected set; } + + #endregion + + /// + /// Fired when the Codec is ready to be used + /// + public event EventHandler IsReadyChange; + + /// + /// Dials the specified meeting + /// + /// + public abstract void Dial(Meeting meeting); + + /// + /// Dial a contact + /// + /// The contact to dial + public virtual void Dial(IInvitableContact contact) + { + } + + /// + public abstract void ExecuteSwitch(object selector); + + /// + /// Helper method to fire CallStatusChange event with old and new status + /// + protected void SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus newStatus, CodecActiveCallItem call) + { + call.Status = newStatus; + + OnCallStatusChange(call); + } + + /// + /// Sets the previous and new status for the call + /// + /// + protected virtual void OnCallStatusChange(CodecActiveCallItem item) + { + CallStatusChange?.Invoke(this, new CodecCallStatusItemChangeEventArgs(item)); + + PrivacyModeIsOnFeedback.FireUpdate(); + + if (AutoShareContentWhileInCall) + { + StartSharing(); + } + + if (UsageTracker != null) + { + if (IsInCall && !UsageTracker.UsageTrackingStarted) + { + UsageTracker.StartDeviceUsage(); + } + else if (UsageTracker.UsageTrackingStarted && !IsInCall) + { + UsageTracker.EndDeviceUsage(); + } + } + } + + /// + /// Sets IsReady property and fires the event. Used for dependent classes to sync up their data. + /// + protected void SetIsReady() + { + CrestronInvoke.BeginInvoke((o) => + { + try + { + IsReady = true; + IsReadyChange?.Invoke(this, new EventArgs()); + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Error in SetIsReady() : {0}", e); + } + }); + } + + // **** DEBUGGING THINGS **** + /// + /// ListCalls method + /// + public virtual void ListCalls() + { + Debug.LogMessage(LogEventLevel.Debug, this, "Active Calls:"); + + var sb = new StringBuilder(); + foreach (var c in ActiveCalls) + { + sb.AppendFormat("id: {0} number: {1} -- name: {2} status: {3} onHold: {4}\r\n", c.Id, c.Number, c.Name, c.Status, c.IsOnHold); + } + Debug.LogMessage(LogEventLevel.Debug, this, "\n{0}\n", sb.ToString()); + } + + /// + /// Activate standby + /// + public abstract void StandbyActivate(); + + /// + /// Deactivate standby + /// + public abstract void StandbyDeactivate(); + + #region Implementation of IBridgeAdvanced + + /// + public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); + + /// + /// Use this method when using a plain VideoCodecControllerJoinMap + /// + /// codec to link + /// trilist to link + /// join to start with + /// key for custom join maps + /// bridge controller + protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, uint joinStart, string joinMapKey, + EiscApiAdvanced bridge) + { + var joinMap = new VideoCodecControllerJoinMap(joinStart); + + var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); + + if (customJoins != null) + { + joinMap.SetCustomJoinData(customJoins); + } + + bridge?.AddJoinMap(Key, joinMap); + + LinkVideoCodecToApi(codec, trilist, joinMap); + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; }; } - } - /// - /// Gets or sets the MeetingsToDisplay - /// - protected abstract Func SharingSourceFeedbackFunc { get; } - - /// - /// Gets or sets the SharingContentIsOnFeedbackFunc - /// - protected abstract Func SharingContentIsOnFeedbackFunc { get; } - - #region ICodecAudio Members - - /// - /// Set Privacy Mode On - /// - public abstract void PrivacyModeOn(); - - /// - /// Set Privacy Mode Off - /// - public abstract void PrivacyModeOff(); - - /// - /// Toggles the Privacy Mode - /// - public abstract void PrivacyModeToggle(); - - /// - /// Gets or sets the PrivacyModeIsOnFeedback - /// - public BoolFeedback PrivacyModeIsOnFeedback { get; private set; } - - /// - /// Gets or sets the MuteFeedback - /// - public BoolFeedback MuteFeedback { get; private set; } - - /// - /// Sets the Mute state to Off - /// - public abstract void MuteOff(); - - /// - /// Sets the Mute state to On - /// - public abstract void MuteOn(); - - /// - /// Sets the Volume level - /// - public abstract void SetVolume(ushort level); - - /// - /// Gets or sets the VolumeLevelFeedback - /// - public IntFeedback VolumeLevelFeedback { get; private set; } - - /// - /// Toggles the Mute state - /// - public abstract void MuteToggle(); - - /// - /// Decreases the Volume level - /// - public abstract void VolumeDown(bool pressRelease); - - /// - /// Increases the Volume level - /// - public abstract void VolumeUp(bool pressRelease); - - #endregion - - #region IHasContentSharing Members - - /// - /// Starts content sharing - /// - public abstract void StartSharing(); - - /// - /// Stops content sharing - /// - public abstract void StopSharing(); - - /// - /// Gets or sets the AutoShareContentWhileInCall - /// - public bool AutoShareContentWhileInCall { get; protected set; } - - /// - /// Gets or sets the SharingSourceFeedback - /// - public StringFeedback SharingSourceFeedback { get; private set; } - - /// - /// Gets or sets the SharingContentIsOnFeedback - /// - public BoolFeedback SharingContentIsOnFeedback { get; private set; } - - #endregion - - #region IHasDialer Members - - /// - /// Fires when the status of any active, dialing, or incoming call changes or is new - /// - public event EventHandler CallStatusChange; - - /// - /// Returns true when any call is not in state Unknown, Disconnecting, Disconnected - /// - public virtual bool IsInCall - { - get + /// + /// Use this method when you need to pass in a join map that extends VideoCodecControllerJoinMap + /// + /// codec to link + /// trilist to link + /// join map to use + protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - var value = ActiveCalls != null && ActiveCalls.Any(c => c.IsActiveCall); - return value; - } - } + Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist {0}", trilist.ID.ToString("X")); - /// - /// Dials the specified number - /// - public abstract void Dial(string number); + LinkVideoCodecDtmfToApi(trilist, joinMap); - public abstract void Dial(string number, string password); + LinkVideoCodecCallControlsToApi(trilist, joinMap); - /// - /// Ends the specified call - /// - public abstract void EndCall(CodecActiveCallItem call); + LinkVideoCodecContentSharingToApi(trilist, joinMap); - /// - /// Ends all active calls - /// - public abstract void EndAllCalls(); + LinkVideoCodecPrivacyToApi(trilist, joinMap); - /// - /// Accepts the specified call - /// - public abstract void AcceptCall(CodecActiveCallItem call); + LinkVideoCodecVolumeToApi(trilist, joinMap); - /// - /// Rejects the specified call - /// - public abstract void RejectCall(CodecActiveCallItem call); + LinkVideoCodecInfoToApi(trilist, joinMap); - /// - /// Sends DTMF tones - /// - public abstract void SendDtmf(string s); + // Register for this event to link any functions that require the codec to be ready first + codec.IsReadyChange += (o, a) => + { + if (codec is IHasCodecCameras) + { + LinkVideoCodecCameraToApi(codec as IHasCodecCameras, trilist, joinMap); + } + }; - /// - public virtual void SendDtmf(string s, CodecActiveCallItem call) { } - - #endregion - - #region IRoutingMidpoint Members - - /// - /// Gets or sets the InputPorts - /// - public RoutingPortCollection InputPorts { get; private set; } - - /// - /// Gets or sets the OutputPorts - /// - public RoutingPortCollection OutputPorts { get; private set; } - - #endregion - - #region IUsageTracking Members - - /// - /// Gets or sets the UsageTracker - /// - public UsageTracking UsageTracker { get; set; } - - #endregion - - #region iVideoCodecInfo Members - - /// - /// Gets the CodecInfo - /// - public VideoCodecInfo CodecInfo { get; protected set; } - - #endregion - - /// - /// Fired when the Codec is ready to be used - /// - public event EventHandler IsReadyEvent; - - /// - /// Dials the specified meeting - /// - /// - public abstract void Dial(Meeting meeting); - - /// - /// Dial a contact - /// - /// The contact to dial - public virtual void Dial(IInvitableContact contact) - { - } - - /// - public abstract void ExecuteSwitch(object selector); - - /// - /// Helper method to fire CallStatusChange event with old and new status - /// - protected void SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus newStatus, CodecActiveCallItem call) - { - call.Status = newStatus; - - OnCallStatusChange(call); - } - - /// - /// Sets the previous and new status for the call - /// - /// - protected virtual void OnCallStatusChange(CodecActiveCallItem item) - { - CallStatusChange?.Invoke(this, new CodecCallStatusItemChangeEventArgs(item)); - - PrivacyModeIsOnFeedback.FireUpdate(); - - if (AutoShareContentWhileInCall) - { - StartSharing(); - } - - if (UsageTracker != null) - { - if (IsInCall && !UsageTracker.UsageTrackingStarted) + if (codec is ICommunicationMonitor) { - UsageTracker.StartDeviceUsage(); - } - else if (UsageTracker.UsageTrackingStarted && !IsInCall) - { - UsageTracker.EndDeviceUsage(); - } - } - } - - /// - /// Sets IsReady property and fires the event. Used for dependent classes to sync up their data. - /// - protected void SetIsReady() - { - Task.Run(() => - { - try - { - IsReady = true; - IsReadyEvent?.Invoke(this, new IsReadyEventArgs(IsReady)); - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Error in SetIsReady() : {0}", e); - } - }); - } - - // **** DEBUGGING THINGS **** - /// - /// ListCalls method - /// - public virtual void ListCalls() - { - Debug.LogMessage(LogEventLevel.Debug, this, "Active Calls:"); - - var sb = new StringBuilder(); - foreach (var c in ActiveCalls) - { - sb.AppendFormat("id: {0} number: {1} -- name: {2} status: {3} onHold: {4}\r\n", c.Id, c.Number, c.Name, c.Status, c.IsOnHold); - } - Debug.LogMessage(LogEventLevel.Debug, this, "\n{0}\n", sb.ToString()); - } - - /// - /// Activate standby - /// - public abstract void StandbyActivate(); - - /// - /// Deactivate standby - /// - public abstract void StandbyDeactivate(); - - #region Implementation of IBridgeAdvanced - - /// - public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge); - - /// - /// Use this method when using a plain VideoCodecControllerJoinMap - /// - /// codec to link - /// trilist to link - /// join to start with - /// key for custom join maps - /// bridge controller - protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, uint joinStart, string joinMapKey, - EiscApiAdvanced bridge) - { - var joinMap = new VideoCodecControllerJoinMap(joinStart); - - var customJoins = JoinMapHelper.TryGetJoinMapAdvancedForDevice(joinMapKey); - - if (customJoins != null) - { - joinMap.SetCustomJoinData(customJoins); - } - - bridge?.AddJoinMap(Key, joinMap); - - LinkVideoCodecToApi(codec, trilist, joinMap); - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - }; - } - - /// - /// Use this method when you need to pass in a join map that extends VideoCodecControllerJoinMap - /// - /// codec to link - /// trilist to link - /// join map to use - protected void LinkVideoCodecToApi(VideoCodecBase codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Linking to Trilist {0}", trilist.ID.ToString("X")); - - LinkVideoCodecDtmfToApi(trilist, joinMap); - - LinkVideoCodecCallControlsToApi(trilist, joinMap); - - LinkVideoCodecContentSharingToApi(trilist, joinMap); - - LinkVideoCodecPrivacyToApi(trilist, joinMap); - - LinkVideoCodecVolumeToApi(trilist, joinMap); - - LinkVideoCodecInfoToApi(trilist, joinMap); - - // Register for this event to link any functions that require the codec to be ready first - codec.IsReadyEvent += (o, a) => - { - if (codec is IHasCodecCameras) - { - LinkVideoCodecCameraToApi(codec as IHasCodecCameras, trilist, joinMap); - } - }; - - if (codec is ICommunicationMonitor) - { - LinkVideoCodecCommMonitorToApi(codec as ICommunicationMonitor, trilist, joinMap); - } - - - if (codec is IHasCodecSelfView) - { - LinkVideoCodecSelfviewToApi(codec as IHasCodecSelfView, trilist, joinMap); - } - - if (codec is IHasCameraAutoMode) - { - trilist.SetBool(joinMap.CameraSupportsAutoMode.JoinNumber, SupportsCameraAutoMode); - LinkVideoCodecCameraModeToApi(codec as IHasCameraAutoMode, trilist, joinMap); - } - - if (codec is IHasCameraOff) - { - trilist.SetBool(joinMap.CameraSupportsOffMode.JoinNumber, SupportsCameraOff); - LinkVideoCodecCameraOffToApi(codec as IHasCameraOff, trilist, joinMap); - } - - if (codec is IHasCodecLayouts) - { - LinkVideoCodecCameraLayoutsToApi(codec as IHasCodecLayouts, trilist, joinMap); - } - - - if (codec is IHasSelfviewPosition) - { - LinkVideoCodecSelfviewPositionToApi(codec as IHasSelfviewPosition, trilist, joinMap); - } - - if (codec is IHasDirectory) - { - LinkVideoCodecDirectoryToApi(codec as IHasDirectory, trilist, joinMap); - } - - if (codec is IHasScheduleAwareness) - { - LinkVideoCodecScheduleToApi(codec as IHasScheduleAwareness, trilist, joinMap); - } - - if (codec is IHasParticipants) - { - LinkVideoCodecParticipantsToApi(codec as IHasParticipants, trilist, joinMap); - } - - if (codec is IHasFarEndContentStatus) - { - (codec as IHasFarEndContentStatus).ReceivingContent.LinkInputSig(trilist.BooleanInput[joinMap.RecievingContent.JoinNumber]); - } - - if (codec is IHasPhoneDialing) - { - LinkVideoCodecPhoneToApi(codec as IHasPhoneDialing, trilist, joinMap); - } - - if (codec is IHasCallHistory) - { - LinkVideoCodecCallHistoryToApi(codec as IHasCallHistory, trilist, joinMap); - } - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - - if (codec is IHasDirectory) - { - (codec as IHasDirectory).SetCurrentDirectoryToRoot(); + LinkVideoCodecCommMonitorToApi(codec as ICommunicationMonitor, trilist, joinMap); } - if (codec is IHasScheduleAwareness) - { - (codec as IHasScheduleAwareness).GetSchedule(); - } - if (codec is IHasParticipants) + if (codec is IHasCodecSelfView) { - UpdateParticipantsXSig((codec as IHasParticipants).Participants.CurrentParticipants); + LinkVideoCodecSelfviewToApi(codec as IHasCodecSelfView, trilist, joinMap); } if (codec is IHasCameraAutoMode) { trilist.SetBool(joinMap.CameraSupportsAutoMode.JoinNumber, SupportsCameraAutoMode); - - (codec as IHasCameraAutoMode).CameraAutoModeIsOnFeedback.FireUpdate(); - } - - if (codec is IHasCodecSelfView) - { - (codec as IHasCodecSelfView).SelfviewIsOnFeedback.FireUpdate(); - } - - if (codec is IHasCameraAutoMode) - { - (codec as IHasCameraAutoMode).CameraAutoModeIsOnFeedback.FireUpdate(); + LinkVideoCodecCameraModeToApi(codec as IHasCameraAutoMode, trilist, joinMap); } if (codec is IHasCameraOff) { - (codec as IHasCameraOff).CameraIsOffFeedback.FireUpdate(); + trilist.SetBool(joinMap.CameraSupportsOffMode.JoinNumber, SupportsCameraOff); + LinkVideoCodecCameraOffToApi(codec as IHasCameraOff, trilist, joinMap); + } + + if (codec is IHasCodecLayouts) + { + LinkVideoCodecCameraLayoutsToApi(codec as IHasCodecLayouts, trilist, joinMap); + } + + + if (codec is IHasSelfviewPosition) + { + LinkVideoCodecSelfviewPositionToApi(codec as IHasSelfviewPosition, trilist, joinMap); + } + + if (codec is IHasDirectory) + { + LinkVideoCodecDirectoryToApi(codec as IHasDirectory, trilist, joinMap); + } + + if (codec is IHasScheduleAwareness) + { + LinkVideoCodecScheduleToApi(codec as IHasScheduleAwareness, trilist, joinMap); + } + + if (codec is IHasParticipants) + { + LinkVideoCodecParticipantsToApi(codec as IHasParticipants, trilist, joinMap); + } + + if (codec is IHasFarEndContentStatus) + { + (codec as IHasFarEndContentStatus).ReceivingContent.LinkInputSig(trilist.BooleanInput[joinMap.RecievingContent.JoinNumber]); } if (codec is IHasPhoneDialing) { - (codec as IHasPhoneDialing).PhoneOffHookFeedback.FireUpdate(); + LinkVideoCodecPhoneToApi(codec as IHasPhoneDialing, trilist, joinMap); } if (codec is IHasCallHistory) { - UpdateCallHistory((codec as IHasCallHistory), trilist, joinMap); + LinkVideoCodecCallHistoryToApi(codec as IHasCallHistory, trilist, joinMap); } - SharingContentIsOnFeedback.FireUpdate(); - }; - } - - private void LinkVideoCodecInfoToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); - trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); - trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); - trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); - trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); - trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); - trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); - - trilist.OnlineStatusChange += (o, a) => - { - if (a.DeviceOnLine) + trilist.OnlineStatusChange += (device, args) => { - trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); - trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); - trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); - trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); - trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); - trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); - trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); - } - }; - } + if (!args.DeviceOnLine) return; - private void LinkVideoCodecPhoneToApi(IHasPhoneDialing codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - codec.PhoneOffHookFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PhoneHookState.JoinNumber]); + if (codec is IHasDirectory) + { + (codec as IHasDirectory).SetCurrentDirectoryToRoot(); + } - trilist.SetSigFalseAction(joinMap.DialPhone.JoinNumber, - () => codec.DialPhoneCall(trilist.StringOutput[joinMap.PhoneDialString.JoinNumber].StringValue)); + if (codec is IHasScheduleAwareness) + { + (codec as IHasScheduleAwareness).GetSchedule(); + } - trilist.SetSigFalseAction(joinMap.HangUpPhone.JoinNumber, codec.EndPhoneCall); - } + if (codec is IHasParticipants) + { + UpdateParticipantsXSig((codec as IHasParticipants).Participants.CurrentParticipants); + } - private void LinkVideoCodecSelfviewPositionToApi(IHasSelfviewPosition codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.SelfviewPosition.JoinNumber, codec.SelfviewPipPositionToggle); + if (codec is IHasCameraAutoMode) + { + trilist.SetBool(joinMap.CameraSupportsAutoMode.JoinNumber, SupportsCameraAutoMode); - codec.SelfviewPipPositionFeedback.LinkInputSig(trilist.StringInput[joinMap.SelfviewPositionFb.JoinNumber]); - } + (codec as IHasCameraAutoMode).CameraAutoModeIsOnFeedback.FireUpdate(); + } - private void LinkVideoCodecCameraOffToApi(IHasCameraOff codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.CameraModeOff.JoinNumber, codec.CameraOff); + if (codec is IHasCodecSelfView) + { + (codec as IHasCodecSelfView).SelfviewIsOnFeedback.FireUpdate(); + } - codec.CameraIsOffFeedback.OutputChange += (o, a) => + if (codec is IHasCameraAutoMode) + { + (codec as IHasCameraAutoMode).CameraAutoModeIsOnFeedback.FireUpdate(); + } + + if (codec is IHasCameraOff) + { + (codec as IHasCameraOff).CameraIsOffFeedback.FireUpdate(); + } + + if (codec is IHasPhoneDialing) + { + (codec as IHasPhoneDialing).PhoneOffHookFeedback.FireUpdate(); + } + + if (codec is IHasCallHistory) + { + UpdateCallHistory((codec as IHasCallHistory), trilist, joinMap); + } + + SharingContentIsOnFeedback.FireUpdate(); + }; + } + + private void LinkVideoCodecInfoToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - if (a.BoolValue) + trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); + trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); + trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); + trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); + trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); + trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); + trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); + + trilist.OnlineStatusChange += (o, a) => + { + if (a.DeviceOnLine) + { + trilist.SetBool(joinMap.MultiSiteOptionIsEnabled.JoinNumber, this.CodecInfo.MultiSiteOptionIsEnabled); + trilist.SetBool(joinMap.AutoAnswerEnabled.JoinNumber, this.CodecInfo.AutoAnswerEnabled); + trilist.SetString(joinMap.DeviceIpAddresss.JoinNumber, this.CodecInfo.IpAddress); + trilist.SetString(joinMap.SipPhoneNumber.JoinNumber, this.CodecInfo.SipPhoneNumber); + trilist.SetString(joinMap.E164Alias.JoinNumber, this.CodecInfo.E164Alias); + trilist.SetString(joinMap.H323Id.JoinNumber, this.CodecInfo.H323Id); + trilist.SetString(joinMap.SipUri.JoinNumber, this.CodecInfo.SipUri); + } + }; + } + + private void LinkVideoCodecPhoneToApi(IHasPhoneDialing codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.PhoneOffHookFeedback.LinkInputSig(trilist.BooleanInput[joinMap.PhoneHookState.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.DialPhone.JoinNumber, + () => codec.DialPhoneCall(trilist.StringOutput[joinMap.PhoneDialString.JoinNumber].StringValue)); + + trilist.SetSigFalseAction(joinMap.HangUpPhone.JoinNumber, codec.EndPhoneCall); + } + + private void LinkVideoCodecSelfviewPositionToApi(IHasSelfviewPosition codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.SelfviewPosition.JoinNumber, codec.SelfviewPipPositionToggle); + + codec.SelfviewPipPositionFeedback.LinkInputSig(trilist.StringInput[joinMap.SelfviewPositionFb.JoinNumber]); + } + + private void LinkVideoCodecCameraOffToApi(IHasCameraOff codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.CameraModeOff.JoinNumber, codec.CameraOff); + + codec.CameraIsOffFeedback.OutputChange += (o, a) => + { + if (a.BoolValue) + { + trilist.SetBool(joinMap.CameraModeOff.JoinNumber, true); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, false); + return; + } + + trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); + + + if (!(codec is IHasCameraAutoMode autoCodec)) return; + + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, autoCodec.CameraAutoModeIsOnFeedback.BoolValue); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !autoCodec.CameraAutoModeIsOnFeedback.BoolValue); + }; + + if (codec.CameraIsOffFeedback.BoolValue) { trilist.SetBool(joinMap.CameraModeOff.JoinNumber, true); trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); @@ -685,965 +699,984 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingSource, trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); - if (!(codec is IHasCameraAutoMode autoCodec)) return; + if (!(codec is IHasCameraAutoMode autoModeCodec)) return; - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, autoCodec.CameraAutoModeIsOnFeedback.BoolValue); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !autoCodec.CameraAutoModeIsOnFeedback.BoolValue); - }; - - if (codec.CameraIsOffFeedback.BoolValue) - { - trilist.SetBool(joinMap.CameraModeOff.JoinNumber, true); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, false); - return; + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, autoModeCodec.CameraAutoModeIsOnFeedback.BoolValue); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !autoModeCodec.CameraAutoModeIsOnFeedback.BoolValue); } - trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); + private void LinkVideoCodecVolumeToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); + MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + trilist.SetSigFalseAction(joinMap.VolumeMuteOn.JoinNumber, MuteOn); + trilist.SetSigFalseAction(joinMap.VolumeMuteOff.JoinNumber, MuteOff); + trilist.SetSigFalseAction(joinMap.VolumeMuteToggle.JoinNumber, MuteToggle); - if (!(codec is IHasCameraAutoMode autoModeCodec)) return; + VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, autoModeCodec.CameraAutoModeIsOnFeedback.BoolValue); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !autoModeCodec.CameraAutoModeIsOnFeedback.BoolValue); - } + trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, VolumeUp); + trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, VolumeDown); - private void LinkVideoCodecVolumeToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - MuteFeedback.LinkInputSig(trilist.BooleanInput[joinMap.VolumeMuteOn.JoinNumber]); - MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]); + trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, SetVolume); - trilist.SetSigFalseAction(joinMap.VolumeMuteOn.JoinNumber, MuteOn); - trilist.SetSigFalseAction(joinMap.VolumeMuteOff.JoinNumber, MuteOff); - trilist.SetSigFalseAction(joinMap.VolumeMuteToggle.JoinNumber, MuteToggle); + } - VolumeLevelFeedback.LinkInputSig(trilist.UShortInput[joinMap.VolumeLevel.JoinNumber]); + private void LinkVideoCodecPrivacyToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + PrivacyModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.MicMuteOn.JoinNumber]); + PrivacyModeIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.MicMuteOff.JoinNumber]); - trilist.SetBoolSigAction(joinMap.VolumeUp.JoinNumber, VolumeUp); - trilist.SetBoolSigAction(joinMap.VolumeDown.JoinNumber, VolumeDown); + trilist.SetSigFalseAction(joinMap.MicMuteOn.JoinNumber, PrivacyModeOn); + trilist.SetSigFalseAction(joinMap.MicMuteOff.JoinNumber, PrivacyModeOff); + trilist.SetSigFalseAction(joinMap.MicMuteToggle.JoinNumber, PrivacyModeToggle); + } - trilist.SetUShortSigAction(joinMap.VolumeLevel.JoinNumber, SetVolume); + private void LinkVideoCodecCommMonitorToApi(ICommunicationMonitor codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); + } - } + private void LinkVideoCodecParticipantsToApi(IHasParticipants codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + // make sure to update the values when the EISC comes online + trilist.OnlineStatusChange += (sender, args) => + { + if (sender.IsOnline) + { + UpdateParticipantsXSig(codec, trilist, joinMap); + } + }; - private void LinkVideoCodecPrivacyToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - PrivacyModeIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.MicMuteOn.JoinNumber]); - PrivacyModeIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.MicMuteOff.JoinNumber]); - - trilist.SetSigFalseAction(joinMap.MicMuteOn.JoinNumber, PrivacyModeOn); - trilist.SetSigFalseAction(joinMap.MicMuteOff.JoinNumber, PrivacyModeOff); - trilist.SetSigFalseAction(joinMap.MicMuteToggle.JoinNumber, PrivacyModeToggle); - } - - private void LinkVideoCodecCommMonitorToApi(ICommunicationMonitor codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - codec.CommunicationMonitor.IsOnlineFeedback.LinkInputSig(trilist.BooleanInput[joinMap.IsOnline.JoinNumber]); - } - - private void LinkVideoCodecParticipantsToApi(IHasParticipants codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - // make sure to update the values when the EISC comes online - trilist.OnlineStatusChange += (sender, args) => + // set actions and update the values when the list changes + codec.Participants.ParticipantsListHasChanged += (sender, args) => { - if (sender.IsOnline) - { - UpdateParticipantsXSig(codec, trilist, joinMap); - } + SetParticipantActions(trilist, joinMap, codec.Participants.CurrentParticipants); + + UpdateParticipantsXSig(codec, trilist, joinMap); }; - // set actions and update the values when the list changes - codec.Participants.ParticipantsListHasChanged += (sender, args) => - { - SetParticipantActions(trilist, joinMap, codec.Participants.CurrentParticipants); - - UpdateParticipantsXSig(codec, trilist, joinMap); - }; - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - - trilist.SetString(joinMap.CurrentParticipants.JoinNumber, "\xFC"); - UpdateParticipantsXSig(codec, trilist, joinMap); - }; - } - - private void UpdateParticipantsXSig(IHasParticipants codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - string participantsXSig; - - if (codec.Participants.CurrentParticipants.Count == 0) - { - participantsXSig = Encoding.GetEncoding(XSigEncoding).GetString(_clearBytes, 0, _clearBytes.Length); - trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig); - trilist.SetUshort(joinMap.ParticipantCount.JoinNumber, (ushort)codec.Participants.CurrentParticipants.Count); - return; - } - - participantsXSig = UpdateParticipantsXSig(codec.Participants.CurrentParticipants); - - trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig); - - trilist.SetUshort(joinMap.ParticipantCount.JoinNumber, (ushort)codec.Participants.CurrentParticipants.Count); - } - - /// - /// Sets the actions for each participant in the list - /// - private void SetParticipantActions(BasicTriList trilist, VideoCodecControllerJoinMap joinMap, List currentParticipants) - { - uint index = 0; // track the index of the participant in the - - foreach (var participant in currentParticipants) - { - var p = participant; - if (index > MaxParticipants) break; - - if (this is IHasParticipantAudioMute audioMuteCodec) + trilist.OnlineStatusChange += (device, args) => { - trilist.SetSigFalseAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index, - () => audioMuteCodec.ToggleAudioForParticipant(p.UserId)); + if (!args.DeviceOnLine) return; - trilist.SetSigFalseAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index, - () => audioMuteCodec.ToggleVideoForParticipant(p.UserId)); - } - - if (this is IHasParticipantPinUnpin pinCodec) - { - trilist.SetSigFalseAction(joinMap.ParticipantPinToggleStart.JoinNumber + index, - () => pinCodec.ToggleParticipantPinState(p.UserId, pinCodec.ScreenIndexToPinUserTo)); - } - - index++; - } - - // Clear out any previously set actions - while (index < MaxParticipants) - { - trilist.ClearBoolSigAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index); - trilist.ClearBoolSigAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index); - trilist.ClearBoolSigAction(joinMap.ParticipantPinToggleStart.JoinNumber + index); - - index++; - } - } - - private string UpdateParticipantsXSig(List currentParticipants) - { - const int maxParticipants = MaxParticipants; - const int maxDigitals = 7; - const int maxStrings = 1; - const int maxAnalogs = 1; - const int offset = maxDigitals + maxStrings + maxAnalogs; // 9 - var digitalIndex = (maxStrings + maxAnalogs) * maxParticipants; // 100 - var stringIndex = 0; - var analogIndex = stringIndex + maxParticipants; - var meetingIndex = 0; - - var tokenArray = new XSigToken[maxParticipants * offset]; - - foreach (var participant in currentParticipants) - { - if (meetingIndex >= maxParticipants * offset) break; - - // Debug.LogMessage(LogEventLevel.Verbose, this, - //@"Updating Participant on xsig: - //Name: {0} (s{9}) - //AudioMute: {1} (d{10}) - //VideoMute: {2} (d{11}) - //CanMuteVideo: {3} (d{12}) - //CanUMuteVideo: {4} (d{13}) - //IsHost: {5} (d{14}) - //HandIsRaised: {6} (d{15}) - //IsPinned: {7} (d{16}) - //ScreenIndexIsPinnedTo: {8} (a{17}) - //", - // participant.Name, - // participant.AudioMuteFb, - // participant.VideoMuteFb, - // participant.CanMuteVideo, - // participant.CanUnmuteVideo, - // participant.IsHost, - // participant.HandIsRaisedFb, - // participant.IsPinnedFb, - // participant.ScreenIndexIsPinnedToFb, - // stringIndex + 1, - // digitalIndex + 1, - // digitalIndex + 2, - // digitalIndex + 3, - // digitalIndex + 4, - // digitalIndex + 5, - // digitalIndex + 6, - // digitalIndex + 7, - // analogIndex + 1 - // ); - - - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, participant.AudioMuteFb); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, participant.VideoMuteFb); - tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, participant.CanMuteVideo); - tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, participant.CanUnmuteVideo); - tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, participant.IsHost); - tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, participant.HandIsRaisedFb); - tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, participant.IsPinnedFb); - - Debug.LogMessage(LogEventLevel.Verbose, this, "Index: {0} byte value: {1}", digitalIndex + 7, ComTextHelper.GetEscapedText(tokenArray[digitalIndex + 6].GetBytes())); - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, participant.Name); - - //analogs - tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, (ushort)participant.ScreenIndexIsPinnedToFb); - - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - analogIndex += maxAnalogs; - } - - while (meetingIndex < maxParticipants * offset) - { - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); - tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, false); - tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, false); - tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, false); - tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, false); - tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, false); - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); - - //analogs - tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, 0); - - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - analogIndex += maxAnalogs; - } - - var returnString = GetXSigString(tokenArray); - - //Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", ComTextHelper.GetEscapedText(Encoding.GetEncoding(28591).GetBytes(returnString))); - - - return returnString; - } - - private void LinkVideoCodecContentSharingToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - SharingContentIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SourceShareStart.JoinNumber]); - SharingContentIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.SourceShareEnd.JoinNumber]); - - SharingSourceFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentSource.JoinNumber]); - - trilist.SetSigFalseAction(joinMap.SourceShareStart.JoinNumber, StartSharing); - trilist.SetSigFalseAction(joinMap.SourceShareEnd.JoinNumber, StopSharing); - - trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, b => AutoShareContentWhileInCall = b); - } - - private List _currentMeetings = new List(); - - private void LinkVideoCodecScheduleToApi(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.UpdateMeetings.JoinNumber, codec.GetSchedule); - - trilist.SetUShortSigAction(joinMap.MinutesBeforeMeetingStart.JoinNumber, (i) => - { - codec.CodecSchedule.MeetingWarningMinutes = i; - }); - - - for (uint i = 0; i < joinMap.DialMeetingStart.JoinSpan; i++) - { - Debug.LogMessage(LogEventLevel.Debug, this, "Setting action to Dial Meeting {0} to digital join {1}", i + 1, joinMap.DialMeetingStart.JoinNumber + i); - var joinNumber = joinMap.DialMeetingStart.JoinNumber + i; - var mtg = i + 1; - var index = (int)i; - - trilist.SetSigFalseAction(joinNumber, () => - { - Debug.LogMessage(LogEventLevel.Debug, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", - mtg, joinMap.DialMeetingStart.JoinNumber + i, index, _currentMeetings[index].Id, _currentMeetings[index].Title); - if (_currentMeetings[index] != null) - Dial(_currentMeetings[index]); - }); - } - - codec.CodecSchedule.MeetingsListHasChanged += (sender, args) => UpdateMeetingsList(codec, trilist, joinMap); - codec.CodecSchedule.MeetingEventChange += (sender, args) => - { - if (args.ChangeType == eMeetingEventChangeType.MeetingStartWarning) - { - UpdateMeetingsList(codec, trilist, joinMap); - } + // TODO [ ] Issue #868 + trilist.SetString(joinMap.CurrentParticipants.JoinNumber, "\xFC"); + UpdateParticipantsXSig(codec, trilist, joinMap); }; - - trilist.SetUShortSigAction(joinMap.MeetingsToDisplay.JoinNumber, m => MeetingsToDisplay = m); - MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - - trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); - UpdateMeetingsList(codec, trilist, joinMap); - MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); - }; - } - - private void UpdateMeetingsList(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - var currentTime = DateTime.Now; - - _currentMeetings = codec.CodecSchedule.Meetings.Where(m => m.StartTime >= currentTime || m.EndTime >= currentTime).ToList(); - - if (_currentMeetings.Count == 0) - { - var emptyXSigByteArray = XSigHelpers.ClearOutputs(); - var emptyXSigString = Encoding.GetEncoding(XSigEncoding) - .GetString(emptyXSigByteArray, 0, emptyXSigByteArray.Length); - - trilist.SetString(joinMap.Schedule.JoinNumber, emptyXSigString); - return; } - var meetingsData = UpdateMeetingsListXSig(_currentMeetings); - trilist.SetString(joinMap.Schedule.JoinNumber, meetingsData); - trilist.SetUshort(joinMap.MeetingCount.JoinNumber, (ushort)_currentMeetings.Count); - - trilist.OnlineStatusChange += (device, args) => + private void UpdateParticipantsXSig(IHasParticipants codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - if (!args.DeviceOnLine) return; + string participantsXSig; - trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); - UpdateMeetingsListXSig(_currentMeetings); - }; - } - - private int _meetingsToDisplay = 3; - - /// - protected int MeetingsToDisplay - { - get { return _meetingsToDisplay; } - set - { - _meetingsToDisplay = (ushort)(value == 0 ? 3 : value); - MeetingsToDisplayFeedback.FireUpdate(); - } - } - - - - /// - public IntFeedback MeetingsToDisplayFeedback { get; set; } - - private string UpdateMeetingsListXSig(List meetings) - { - //const int _meetingsToDisplay = 3; - const int maxDigitals = 2; - const int maxStrings = 7; - const int offset = maxDigitals + maxStrings; - var digitalIndex = maxStrings * _meetingsToDisplay; //15 - var stringIndex = 0; - var meetingIndex = 0; - - var tokenArray = new XSigToken[_meetingsToDisplay * offset]; - /* - * Digitals - * IsJoinable - 1 - * IsDialable - 2 - * - * Serials - * Organizer - 1 - * Title - 2 - * Start Date - 3 - * Start Time - 4 - * End Date - 5 - * End Time - 6 - * Id - 7 - */ - - - foreach (var meeting in meetings) - { - var currentTime = DateTime.Now; - - if (meeting.StartTime < currentTime && meeting.EndTime < currentTime) continue; - - if (meetingIndex >= _meetingsToDisplay * offset) + if (codec.Participants.CurrentParticipants.Count == 0) { - Debug.LogMessage(LogEventLevel.Verbose, this, "Max Meetings reached"); - break; - } - - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, meeting.Joinable); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, meeting.Dialable); - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, meeting.Organizer); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, meeting.Title); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, meeting.StartTime.ToString(string.IsNullOrEmpty(_dateFormatSpecifier) ? "d" : _dateFormatSpecifier, Global.Culture)); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, meeting.StartTime.ToString(string.IsNullOrEmpty(_timeFormatSpecifier) ? "t" : _timeFormatSpecifier, Global.Culture)); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, meeting.EndTime.ToString(string.IsNullOrEmpty(_dateFormatSpecifier) ? "d" : _dateFormatSpecifier, Global.Culture)); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, meeting.EndTime.ToString(string.IsNullOrEmpty(_timeFormatSpecifier) ? "t" : _timeFormatSpecifier, Global.Culture)); - tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, meeting.Id); - - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - } - - while (meetingIndex < _meetingsToDisplay * offset) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Clearing unused data. Meeting Index: {0} MaxMeetings * Offset: {1}", - meetingIndex, _meetingsToDisplay * offset); - - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); - tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, String.Empty); - - digitalIndex += maxDigitals; - meetingIndex += offset; - stringIndex += maxStrings; - } - - return GetXSigString(tokenArray); - } - - - private void LinkVideoCodecDirectoryToApi(IHasDirectory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - codec.CurrentDirectoryResultIsNotDirectoryRoot.LinkComplementInputSig( - trilist.BooleanInput[joinMap.DirectoryIsRoot.JoinNumber]); - - trilist.SetStringSigAction(joinMap.DirectorySearchString.JoinNumber, codec.SearchDirectory); - - trilist.SetUShortSigAction(joinMap.DirectorySelectRow.JoinNumber, (i) => SelectDirectoryEntry(codec, i, trilist, joinMap)); - - //Special Change for protected directory clear - - trilist.SetBoolSigAction(joinMap.DirectoryClearSelected.JoinNumber, (b) => SelectDirectoryEntry(_directoryCodec, 0, _directoryTrilist, _directoryJoinmap)); - - // Report feedback for number of contact methods for selected contact - - trilist.SetSigFalseAction(joinMap.DirectoryRoot.JoinNumber, codec.SetCurrentDirectoryToRoot); - - trilist.SetSigFalseAction(joinMap.DirectoryFolderBack.JoinNumber, codec.GetDirectoryParentFolderContents); - - if (codec.DirectoryRoot != null) - { - var contactsCount = codec.DirectoryRoot.CurrentDirectoryResults.Where(c => c.ParentFolderId.Equals("root")).ToList().Count; - trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)contactsCount); - Debug.LogMessage(LogEventLevel.Verbose, this, ">>> contactsCount: {0}", contactsCount); - - var clearBytes = XSigHelpers.ClearOutputs(); - - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, - Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); - var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, - codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); - - Debug.LogMessage(LogEventLevel.Verbose, this, "Directory XSig Length: {0}", directoryXSig.Length); - - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); - } - - codec.DirectoryResultReturned += (sender, args) => - { - var isRoot = codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false; - var argsCount = isRoot - ? args.Directory.CurrentDirectoryResults.Where(a => a.ParentFolderId.Equals("root")).ToList().Count - : args.Directory.CurrentDirectoryResults.Count; - - trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)argsCount); - Debug.LogMessage(LogEventLevel.Verbose, this, ">>> argsCount: {0}", argsCount); - - var clearBytes = XSigHelpers.ClearOutputs(); - - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, - Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); - var directoryXSig = UpdateDirectoryXSig(args.Directory, - codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); - Debug.LogMessage(LogEventLevel.Verbose, this, "Directory XSig Length: {0}", directoryXSig.Length); - - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); - }; - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - - var clearBytes = XSigHelpers.ClearOutputs(); - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, - Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); - var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); - trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); - }; - } - - private void SelectDirectoryEntry(IHasDirectory codec, ushort i, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - if (i > codec.CurrentDirectoryResult.CurrentDirectoryResults.Count) return; - _selectedDirectoryItem = i == 0 ? null : codec.CurrentDirectoryResult.CurrentDirectoryResults[i - 1]; - trilist.SetUshort(joinMap.DirectorySelectRowFeedback.JoinNumber, i); - - if (_selectedDirectoryItem == null) trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); - - - if (_selectedDirectoryItem is DirectoryFolder) - { - codec.GetDirectoryFolderContents(_selectedDirectoryItem.FolderId); - trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, 0); - trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, _selectedDirectoryItem.Name); - trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, string.Empty); - trilist.ClearUShortSigAction(joinMap.SelectContactMethod.JoinNumber); - trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedLine.JoinNumber); - trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber); - trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); - return; - } - - // not a folder. Clear this value - trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, string.Empty); - - var selectedContact = _selectedDirectoryItem as DirectoryContact; - - if (selectedContact != null && selectedContact.ContactMethods.Count >= 1) - { - trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, true); - } - - trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, - selectedContact != null ? selectedContact.Name : string.Empty); - - // Allow auto dial of selected line. Always dials first contact method - if (!trilist.GetBool(joinMap.DirectoryDisableAutoDialSelectedLine.JoinNumber)) - { - if (_selectedDirectoryItem is IInvitableContact invitableEntry) - { - Dial(invitableEntry); + participantsXSig = Encoding.GetEncoding(XSigEncoding).GetString(_clearBytes, 0, _clearBytes.Length); + trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig); + trilist.SetUshort(joinMap.ParticipantCount.JoinNumber, (ushort)codec.Participants.CurrentParticipants.Count); return; } + participantsXSig = UpdateParticipantsXSig(codec.Participants.CurrentParticipants); - trilist.SetString(joinMap.DirectoryEntrySelectedNumber.JoinNumber, - selectedContact != null ? selectedContact.ContactMethods[0].Number : string.Empty); + trilist.SetString(joinMap.CurrentParticipants.JoinNumber, participantsXSig); - if (!(_selectedDirectoryItem is DirectoryContact entryToDial)) return; - - Dial(entryToDial.ContactMethods[0].Number); + trilist.SetUshort(joinMap.ParticipantCount.JoinNumber, (ushort)codec.Participants.CurrentParticipants.Count); } - else - { - // If auto dial is disabled... - if (!(_selectedDirectoryItem is DirectoryContact entryToDial)) + /// + /// Sets the actions for each participant in the list + /// + private void SetParticipantActions(BasicTriList trilist, VideoCodecControllerJoinMap joinMap, List currentParticipants) + { + uint index = 0; // track the index of the participant in the + + foreach (var participant in currentParticipants) { - // Clear out values and actions from last selected item + var p = participant; + if (index > MaxParticipants) break; + + if (this is IHasParticipantAudioMute audioMuteCodec) + { + trilist.SetSigFalseAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index, + () => audioMuteCodec.ToggleAudioForParticipant(p.UserId)); + + trilist.SetSigFalseAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index, + () => audioMuteCodec.ToggleVideoForParticipant(p.UserId)); + } + + if (this is IHasParticipantPinUnpin pinCodec) + { + trilist.SetSigFalseAction(joinMap.ParticipantPinToggleStart.JoinNumber + index, + () => pinCodec.ToggleParticipantPinState(p.UserId, pinCodec.ScreenIndexToPinUserTo)); + } + + index++; + } + + // Clear out any previously set actions + while (index < MaxParticipants) + { + trilist.ClearBoolSigAction(joinMap.ParticipantAudioMuteToggleStart.JoinNumber + index); + trilist.ClearBoolSigAction(joinMap.ParticipantVideoMuteToggleStart.JoinNumber + index); + trilist.ClearBoolSigAction(joinMap.ParticipantPinToggleStart.JoinNumber + index); + + index++; + } + } + + private string UpdateParticipantsXSig(List currentParticipants) + { + const int maxParticipants = MaxParticipants; + const int maxDigitals = 7; + const int maxStrings = 1; + const int maxAnalogs = 1; + const int offset = maxDigitals + maxStrings + maxAnalogs; // 9 + var digitalIndex = (maxStrings + maxAnalogs) * maxParticipants; // 100 + var stringIndex = 0; + var analogIndex = stringIndex + maxParticipants; + var meetingIndex = 0; + + var tokenArray = new XSigToken[maxParticipants * offset]; + + foreach (var participant in currentParticipants) + { + if (meetingIndex >= maxParticipants * offset) break; + + // Debug.LogMessage(LogEventLevel.Verbose, this, + //@"Updating Participant on xsig: + //Name: {0} (s{9}) + //AudioMute: {1} (d{10}) + //VideoMute: {2} (d{11}) + //CanMuteVideo: {3} (d{12}) + //CanUMuteVideo: {4} (d{13}) + //IsHost: {5} (d{14}) + //HandIsRaised: {6} (d{15}) + //IsPinned: {7} (d{16}) + //ScreenIndexIsPinnedTo: {8} (a{17}) + //", + // participant.Name, + // participant.AudioMuteFb, + // participant.VideoMuteFb, + // participant.CanMuteVideo, + // participant.CanUnmuteVideo, + // participant.IsHost, + // participant.HandIsRaisedFb, + // participant.IsPinnedFb, + // participant.ScreenIndexIsPinnedToFb, + // stringIndex + 1, + // digitalIndex + 1, + // digitalIndex + 2, + // digitalIndex + 3, + // digitalIndex + 4, + // digitalIndex + 5, + // digitalIndex + 6, + // digitalIndex + 7, + // analogIndex + 1 + // ); + + + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, participant.AudioMuteFb); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, participant.VideoMuteFb); + tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, participant.CanMuteVideo); + tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, participant.CanUnmuteVideo); + tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, participant.IsHost); + tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, participant.HandIsRaisedFb); + tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, participant.IsPinnedFb); + + Debug.LogMessage(LogEventLevel.Verbose, this, "Index: {0} byte value: {1}", digitalIndex + 7, ComTextHelper.GetEscapedText(tokenArray[digitalIndex + 6].GetBytes())); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, participant.Name); + + //analogs + tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, (ushort)participant.ScreenIndexIsPinnedToFb); + + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + analogIndex += maxAnalogs; + } + + while (meetingIndex < maxParticipants * offset) + { + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); + tokenArray[digitalIndex + 2] = new XSigDigitalToken(digitalIndex + 3, false); + tokenArray[digitalIndex + 3] = new XSigDigitalToken(digitalIndex + 4, false); + tokenArray[digitalIndex + 4] = new XSigDigitalToken(digitalIndex + 5, false); + tokenArray[digitalIndex + 5] = new XSigDigitalToken(digitalIndex + 6, false); + tokenArray[digitalIndex + 6] = new XSigDigitalToken(digitalIndex + 7, false); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); + + //analogs + tokenArray[analogIndex] = new XSigAnalogToken(analogIndex + 1, 0); + + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + analogIndex += maxAnalogs; + } + + var returnString = GetXSigString(tokenArray); + + //Debug.LogMessage(LogEventLevel.Verbose, this, "{0}", ComTextHelper.GetEscapedText(Encoding.GetEncoding(28591).GetBytes(returnString))); + + + return returnString; + } + + private void LinkVideoCodecContentSharingToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + SharingContentIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.SourceShareStart.JoinNumber]); + SharingContentIsOnFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.SourceShareEnd.JoinNumber]); + + SharingSourceFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentSource.JoinNumber]); + + trilist.SetSigFalseAction(joinMap.SourceShareStart.JoinNumber, StartSharing); + trilist.SetSigFalseAction(joinMap.SourceShareEnd.JoinNumber, StopSharing); + + trilist.SetBoolSigAction(joinMap.SourceShareAutoStart.JoinNumber, b => AutoShareContentWhileInCall = b); + } + + private List _currentMeetings = new List(); + + private void LinkVideoCodecScheduleToApi(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.UpdateMeetings.JoinNumber, codec.GetSchedule); + + trilist.SetUShortSigAction(joinMap.MinutesBeforeMeetingStart.JoinNumber, (i) => + { + codec.CodecSchedule.MeetingWarningMinutes = i; + }); + + + for (uint i = 0; i < joinMap.DialMeetingStart.JoinSpan; i++) + { + Debug.LogMessage(LogEventLevel.Debug, this, "Setting action to Dial Meeting {0} to digital join {1}", i + 1, joinMap.DialMeetingStart.JoinNumber + i); + var joinNumber = joinMap.DialMeetingStart.JoinNumber + i; + var mtg = i + 1; + var index = (int)i; + + trilist.SetSigFalseAction(joinNumber, () => + { + Debug.LogMessage(LogEventLevel.Debug, this, "Meeting {0} Selected (EISC dig-o{1}) > _currentMeetings[{2}].Id: {3}, Title: {4}", + mtg, joinMap.DialMeetingStart.JoinNumber + i, index, _currentMeetings[index].Id, _currentMeetings[index].Title); + if (_currentMeetings[index] != null) + Dial(_currentMeetings[index]); + }); + } + + codec.CodecSchedule.MeetingsListHasChanged += (sender, args) => UpdateMeetingsList(codec, trilist, joinMap); + codec.CodecSchedule.MeetingEventChange += (sender, args) => + { + if (args.ChangeType == eMeetingEventChangeType.MeetingStartWarning) + { + UpdateMeetingsList(codec, trilist, joinMap); + } + }; + + trilist.SetUShortSigAction(joinMap.MeetingsToDisplay.JoinNumber, m => MeetingsToDisplay = m); + MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; + + // TODO [ ] Issue #868 + trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); + UpdateMeetingsList(codec, trilist, joinMap); + // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set + MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); + }; + } + + private void UpdateMeetingsList(IHasScheduleAwareness codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + var currentTime = DateTime.Now; + + _currentMeetings = codec.CodecSchedule.Meetings.Where(m => m.StartTime >= currentTime || m.EndTime >= currentTime).ToList(); + + if (_currentMeetings.Count == 0) + { + var emptyXSigByteArray = XSigHelpers.ClearOutputs(); + var emptyXSigString = Encoding.GetEncoding(XSigEncoding) + .GetString(emptyXSigByteArray, 0, emptyXSigByteArray.Length); + + trilist.SetString(joinMap.Schedule.JoinNumber, emptyXSigString); + return; + } + + var meetingsData = UpdateMeetingsListXSig(_currentMeetings); + trilist.SetString(joinMap.Schedule.JoinNumber, meetingsData); + trilist.SetUshort(joinMap.MeetingCount.JoinNumber, (ushort)_currentMeetings.Count); + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; + + // TODO [ ] Issue #868 + trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); + UpdateMeetingsListXSig(_currentMeetings); + }; + } + + + + private int _meetingsToDisplay = 3; + + /// + /// Gets or sets the number of meetings to display on the XSIG + /// + protected int MeetingsToDisplay + { + get { return _meetingsToDisplay; } + set + { + _meetingsToDisplay = (ushort)(value == 0 ? 3 : value); + MeetingsToDisplayFeedback.FireUpdate(); + } + } + + /// + /// Gets or sets the MeetingsToDisplayFeedback + /// + public IntFeedback MeetingsToDisplayFeedback { get; set; } + + private string UpdateMeetingsListXSig(List meetings) + { + // TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set + //const int _meetingsToDisplay = 3; + const int maxDigitals = 2; + const int maxStrings = 7; + const int offset = maxDigitals + maxStrings; + var digitalIndex = maxStrings * _meetingsToDisplay; //15 + var stringIndex = 0; + var meetingIndex = 0; + + var tokenArray = new XSigToken[_meetingsToDisplay * offset]; + /* + * Digitals + * IsJoinable - 1 + * IsDialable - 2 + * + * Serials + * Organizer - 1 + * Title - 2 + * Start Date - 3 + * Start Time - 4 + * End Date - 5 + * End Time - 6 + * Id - 7 + */ + + + foreach (var meeting in meetings) + { + var currentTime = DateTime.Now; + + if (meeting.StartTime < currentTime && meeting.EndTime < currentTime) continue; + + if (meetingIndex >= _meetingsToDisplay * offset) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Max Meetings reached"); + break; + } + + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, meeting.Joinable); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, meeting.Dialable); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, meeting.Organizer); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, meeting.Title); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, meeting.StartTime.ToString(_dateFormatSpecifier.NullIfEmpty() ?? "d", Global.Culture)); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, meeting.StartTime.ToString(_timeFormatSpecifier.NullIfEmpty() ?? "t", Global.Culture)); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, meeting.EndTime.ToString(_dateFormatSpecifier.NullIfEmpty() ?? "d", Global.Culture)); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, meeting.EndTime.ToString(_timeFormatSpecifier.NullIfEmpty() ?? "t", Global.Culture)); + tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, meeting.Id); + + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + } + + while (meetingIndex < _meetingsToDisplay * offset) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Clearing unused data. Meeting Index: {0} MaxMeetings * Offset: {1}", + meetingIndex, _meetingsToDisplay * offset); + + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); + tokenArray[stringIndex + 6] = new XSigSerialToken(stringIndex + 7, String.Empty); + + digitalIndex += maxDigitals; + meetingIndex += offset; + stringIndex += maxStrings; + } + + return GetXSigString(tokenArray); + } + + + private void LinkVideoCodecDirectoryToApi(IHasDirectory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.CurrentDirectoryResultIsNotDirectoryRoot.LinkComplementInputSig( + trilist.BooleanInput[joinMap.DirectoryIsRoot.JoinNumber]); + + trilist.SetStringSigAction(joinMap.DirectorySearchString.JoinNumber, codec.SearchDirectory); + + trilist.SetUShortSigAction(joinMap.DirectorySelectRow.JoinNumber, (i) => SelectDirectoryEntry(codec, i, trilist, joinMap)); + + //Special Change for protected directory clear + + trilist.SetBoolSigAction(joinMap.DirectoryClearSelected.JoinNumber, (b) => SelectDirectoryEntry(codec, 0, trilist, joinMap)); + + // Report feedback for number of contact methods for selected contact + + trilist.SetSigFalseAction(joinMap.DirectoryRoot.JoinNumber, codec.SetCurrentDirectoryToRoot); + + trilist.SetSigFalseAction(joinMap.DirectoryFolderBack.JoinNumber, codec.GetDirectoryParentFolderContents); + + if (codec.DirectoryRoot != null) + { + var contactsCount = codec.DirectoryRoot.CurrentDirectoryResults.Where(c => c.ParentFolderId.Equals("root")).ToList().Count; + trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)contactsCount); + Debug.LogMessage(LogEventLevel.Verbose, this, ">>> contactsCount: {0}", contactsCount); + + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, + codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + + Debug.LogMessage(LogEventLevel.Verbose, this, "Directory XSig Length: {0}", directoryXSig.Length); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); + } + + codec.DirectoryResultReturned += (sender, args) => + { + var isRoot = codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false; + var argsCount = isRoot + ? args.Directory.CurrentDirectoryResults.Where(a => a.ParentFolderId.Equals("root")).ToList().Count + : args.Directory.CurrentDirectoryResults.Count; + + trilist.SetUshort(joinMap.DirectoryRowCount.JoinNumber, (ushort)argsCount); + Debug.LogMessage(LogEventLevel.Verbose, this, ">>> argsCount: {0}", argsCount); + + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var directoryXSig = UpdateDirectoryXSig(args.Directory, + codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + Debug.LogMessage(LogEventLevel.Verbose, this, "Directory XSig Length: {0}", directoryXSig.Length); + + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); + }; + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; + + var clearBytes = XSigHelpers.ClearOutputs(); + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var directoryXSig = UpdateDirectoryXSig(codec.DirectoryRoot, codec.CurrentDirectoryResultIsNotDirectoryRoot.BoolValue == false); + trilist.SetString(joinMap.DirectoryEntries.JoinNumber, directoryXSig); + }; + } + + private void SelectDirectoryEntry(IHasDirectory codec, ushort i, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + if (i > codec.CurrentDirectoryResult.CurrentDirectoryResults.Count) return; + _selectedDirectoryItem = i == 0 ? null : codec.CurrentDirectoryResult.CurrentDirectoryResults[i - 1]; + trilist.SetUshort(joinMap.DirectorySelectRowFeedback.JoinNumber, i); + + if (_selectedDirectoryItem == null) trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); + + + if (_selectedDirectoryItem is DirectoryFolder) + { + codec.GetDirectoryFolderContents(_selectedDirectoryItem.FolderId); trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, 0); + trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, _selectedDirectoryItem.Name); trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, string.Empty); trilist.ClearUShortSigAction(joinMap.SelectContactMethod.JoinNumber); trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedLine.JoinNumber); trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber); + trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, false); return; } - trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, (ushort)entryToDial.ContactMethods.Count); + // not a folder. Clear this value + trilist.SetString(joinMap.DirectorySelectedFolderName.JoinNumber, string.Empty); - // Update the action to dial the selected contact method - trilist.SetUShortSigAction(joinMap.SelectContactMethod.JoinNumber, (u) => + var selectedContact = _selectedDirectoryItem as DirectoryContact; + + if (selectedContact != null && selectedContact.ContactMethods.Count >= 1) { - if (u < 1 || u > entryToDial.ContactMethods.Count) return; - - trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber, () => Dial(entryToDial.ContactMethods[u - 1].Number)); - }); - - // Sets DirectoryDialSelectedLine join action to dial first contact method - trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedLine.JoinNumber, () => Dial(entryToDial.ContactMethods[0].Number)); - - var clearBytes = XSigHelpers.ClearOutputs(); - - trilist.SetString(joinMap.ContactMethods.JoinNumber, - Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); - var contactMethodsXSig = UpdateContactMethodsXSig(entryToDial); - - trilist.SetString(joinMap.ContactMethods.JoinNumber, contactMethodsXSig); - } - } - - /// - /// Generates the XSig data representing the available contact methods for the selected DirectoryContact - /// - /// - /// - private string UpdateContactMethodsXSig(DirectoryContact contact) - { - const int maxMethods = 10; - const int maxStrings = 3; - const int offset = maxStrings; - var stringIndex = 0; - var arrayIndex = 0; - // Create a new token array and set the size to the number of methods times the total number of signals - var tokenArray = new XSigToken[maxMethods * offset]; - - Debug.LogMessage(LogEventLevel.Verbose, this, "Creating XSIG token array with size {0}", maxMethods * offset); - - foreach (var method in contact.ContactMethods) - { - if (arrayIndex >= maxMethods * offset) - break; - - //serials - tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, method.Number); - tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, method.ContactMethodId.ToString()); - tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, method.Device.ToString()); - - arrayIndex += offset; - stringIndex += maxStrings; - } - - while (arrayIndex < maxMethods) - { - tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, String.Empty); - tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, String.Empty); - tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, String.Empty); - - arrayIndex += offset; - stringIndex += maxStrings; - } - - return GetXSigString(tokenArray); - } - - private string UpdateDirectoryXSig(CodecDirectory directory, bool isRoot) - { - var xSigMaxIndex = 1023; - var tokenArray = new XSigToken[directory.CurrentDirectoryResults.Count > xSigMaxIndex - ? xSigMaxIndex - : directory.CurrentDirectoryResults.Count]; - - Debug.LogMessage(LogEventLevel.Verbose, this, "IsRoot: {0}, Directory Count: {1}, TokenArray.Length: {2}", isRoot, directory.CurrentDirectoryResults.Count, tokenArray.Length); - - var contacts = directory.CurrentDirectoryResults.Count > xSigMaxIndex - ? directory.CurrentDirectoryResults.Take(xSigMaxIndex) - : directory.CurrentDirectoryResults; - - var contactsToDisplay = isRoot - ? contacts.Where(c => c.ParentFolderId == "root") - : contacts.Where(c => c.ParentFolderId != "root"); - - var counterIndex = 1; - foreach (var entry in contactsToDisplay) - { - var arrayIndex = counterIndex - 1; - var entryIndex = counterIndex; - - Debug.LogMessage(LogEventLevel.Verbose, this, "Entry{2:0000} Name: {0}, Folder ID: {1}, Type: {3}, ParentFolderId: {4}", - entry.Name, entry.FolderId, entryIndex, entry.GetType().FullName, entry.ParentFolderId); - - if (entry is DirectoryFolder) - { - tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, String.Format("[+] {0}", entry.Name)); - - counterIndex++; - - continue; + trilist.SetBool(joinMap.DirectoryEntryIsContact.JoinNumber, true); } - tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, entry.Name); + trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, + selectedContact != null ? selectedContact.Name : string.Empty); - counterIndex++; - } - - return GetXSigString(tokenArray); - } - - private void LinkVideoCodecCallControlsToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.ManualDial.JoinNumber, - () => Dial(trilist.StringOutput[joinMap.CurrentDialString.JoinNumber].StringValue)); - - //End All calls - trilist.SetSigFalseAction(joinMap.EndAllCalls.JoinNumber, EndAllCalls); - - //End a specific call, specified by index. Maximum 8 calls supported - for (int i = 0; i < joinMap.EndCallStart.JoinSpan; i++) - { - var callIndex = i; - - trilist.SetSigFalseAction((uint)(joinMap.EndCallStart.JoinNumber + i), () => - { - - if (callIndex < 0 || callIndex >= ActiveCalls.Count) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Cannot end call. No call found at index: {0}", callIndex); - return; - } - - var call = ActiveCalls[callIndex]; - if (call != null) - { - EndCall(call); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "[End Call] Unable to find call at index '{0}'", i); - } - }); - } - - trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); - - CallStatusChange += (sender, args) => - { - trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); - - Debug.LogMessage(LogEventLevel.Debug, this, "Call Direction: {0}", args.CallItem.Direction); - Debug.LogMessage(LogEventLevel.Debug, this, "Call is incoming: {0}", args.CallItem.Direction == eCodecCallDirection.Incoming); - trilist.SetBool(joinMap.IncomingCall.JoinNumber, args.CallItem.Direction == eCodecCallDirection.Incoming && args.CallItem.Status == eCodecCallStatus.Ringing); - - if (args.CallItem.Direction == eCodecCallDirection.Incoming) + // Allow auto dial of selected line. Always dials first contact method + if (!trilist.GetBool(joinMap.DirectoryDisableAutoDialSelectedLine.JoinNumber)) { - trilist.SetSigFalseAction(joinMap.IncomingAnswer.JoinNumber, () => AcceptCall(args.CallItem)); - trilist.SetSigFalseAction(joinMap.IncomingReject.JoinNumber, () => RejectCall(args.CallItem)); - trilist.SetString(joinMap.IncomingCallName.JoinNumber, args.CallItem.Name); - trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, args.CallItem.Number); + if (_selectedDirectoryItem is IInvitableContact invitableEntry) + { + Dial(invitableEntry); + return; + } + + + trilist.SetString(joinMap.DirectoryEntrySelectedNumber.JoinNumber, + selectedContact != null ? selectedContact.ContactMethods[0].Number : string.Empty); + + if (!(_selectedDirectoryItem is DirectoryContact entryToDial)) return; + + Dial(entryToDial.ContactMethods[0].Number); } else { - trilist.SetString(joinMap.IncomingCallName.JoinNumber, string.Empty); - trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, string.Empty); - } + // If auto dial is disabled... - - trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); - - trilist.SetUshort(joinMap.ConnectedCallCount.JoinNumber, (ushort)ActiveCalls.Count); - }; - - if (this is IJoinCalls joinCodec) - { - trilist.SetSigFalseAction(joinMap.JoinAllCalls.JoinNumber, () => joinCodec.JoinAllCalls()); - - for (int i = 0; i < joinMap.JoinCallStart.JoinSpan; i++) - { - trilist.SetSigFalseAction((uint)(joinMap.JoinCallStart.JoinNumber + i), () => - { - var call = ActiveCalls[i]; - if (call != null) - { - joinCodec.JoinCall(call); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "[Join Call] Unable to find call at index '{0}'", i); - } - }); - } - } - - if (this is IHasCallHold holdCodec) - { - trilist.SetSigFalseAction(joinMap.HoldAllCalls.JoinNumber, () => - { - foreach (var call in ActiveCalls) + if (!(_selectedDirectoryItem is DirectoryContact entryToDial)) { - holdCodec.HoldCall(call); + // Clear out values and actions from last selected item + trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, 0); + trilist.SetString(joinMap.DirectoryEntrySelectedName.JoinNumber, string.Empty); + trilist.ClearUShortSigAction(joinMap.SelectContactMethod.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedLine.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber); + return; } - }); - for (int i = 0; i < joinMap.HoldCallsStart.JoinSpan; i++) - { - var index = i; + trilist.SetUshort(joinMap.SelectedContactMethodCount.JoinNumber, (ushort)entryToDial.ContactMethods.Count); - trilist.SetSigFalseAction((uint)(joinMap.HoldCallsStart.JoinNumber + index), () => - { - if (index < 0 || index >= ActiveCalls.Count) return; + // Update the action to dial the selected contact method + trilist.SetUShortSigAction(joinMap.SelectContactMethod.JoinNumber, (u) => + { + if (u < 1 || u > entryToDial.ContactMethods.Count) return; - var call = ActiveCalls[index]; - if (call != null) - { - holdCodec.HoldCall(call); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "[Hold Call] Unable to find call at index '{0}'", i); - } - }); + trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedContactMethod.JoinNumber, () => Dial(entryToDial.ContactMethods[u - 1].Number)); + }); - trilist.SetSigFalseAction((uint)(joinMap.ResumeCallsStart.JoinNumber + index), () => - { - if (index < 0 || index >= ActiveCalls.Count) return; + // Sets DirectoryDialSelectedLine join action to dial first contact method + trilist.SetSigFalseAction(joinMap.DirectoryDialSelectedLine.JoinNumber, () => Dial(entryToDial.ContactMethods[0].Number)); - var call = ActiveCalls[index]; - if (call != null) - { - holdCodec.ResumeCall(call); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "[Resume Call] Unable to find call at index '{0}'", i); - } - }); + var clearBytes = XSigHelpers.ClearOutputs(); + + trilist.SetString(joinMap.ContactMethods.JoinNumber, + Encoding.GetEncoding(XSigEncoding).GetString(clearBytes, 0, clearBytes.Length)); + var contactMethodsXSig = UpdateContactMethodsXSig(entryToDial); + + trilist.SetString(joinMap.ContactMethods.JoinNumber, contactMethodsXSig); } } + /// + /// Generates the XSig data representing the available contact methods for the selected DirectoryContact + /// + /// + /// + private string UpdateContactMethodsXSig(DirectoryContact contact) + { + const int maxMethods = 10; + const int maxStrings = 3; + const int offset = maxStrings; + var stringIndex = 0; + var arrayIndex = 0; + // Create a new token array and set the size to the number of methods times the total number of signals + var tokenArray = new XSigToken[maxMethods * offset]; + + Debug.LogMessage(LogEventLevel.Verbose, this, "Creating XSIG token array with size {0}", maxMethods * offset); + + // TODO: Add code to generate XSig data + foreach (var method in contact.ContactMethods) + { + if (arrayIndex >= maxMethods * offset) + break; + + //serials + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, method.Number); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, method.ContactMethodId.ToString()); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, method.Device.ToString()); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + while (arrayIndex < maxMethods) + { + tokenArray[arrayIndex + 1] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[arrayIndex + 2] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[arrayIndex + 3] = new XSigSerialToken(stringIndex + 3, String.Empty); + + arrayIndex += offset; + stringIndex += maxStrings; + } + + return GetXSigString(tokenArray); + } + + private string UpdateDirectoryXSig(CodecDirectory directory, bool isRoot) + { + var xSigMaxIndex = 1023; + var tokenArray = new XSigToken[directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? xSigMaxIndex + : directory.CurrentDirectoryResults.Count]; + + Debug.LogMessage(LogEventLevel.Verbose, this, "IsRoot: {0}, Directory Count: {1}, TokenArray.Length: {2}", isRoot, directory.CurrentDirectoryResults.Count, tokenArray.Length); + + var contacts = directory.CurrentDirectoryResults.Count > xSigMaxIndex + ? directory.CurrentDirectoryResults.Take(xSigMaxIndex) + : directory.CurrentDirectoryResults; + + var contactsToDisplay = isRoot + ? contacts.Where(c => c.ParentFolderId == "root") + : contacts.Where(c => c.ParentFolderId != "root"); + + var counterIndex = 1; + foreach (var entry in contactsToDisplay) + { + var arrayIndex = counterIndex - 1; + var entryIndex = counterIndex; + + Debug.LogMessage(LogEventLevel.Verbose, this, "Entry{2:0000} Name: {0}, Folder ID: {1}, Type: {3}, ParentFolderId: {4}", + entry.Name, entry.FolderId, entryIndex, entry.GetType().FullName, entry.ParentFolderId); + + if (entry is DirectoryFolder) + { + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, String.Format("[+] {0}", entry.Name)); + + counterIndex++; + + continue; + } + + tokenArray[arrayIndex] = new XSigSerialToken(entryIndex, entry.Name); + + counterIndex++; + } + + return GetXSigString(tokenArray); + } + + private void LinkVideoCodecCallControlsToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.ManualDial.JoinNumber, + () => Dial(trilist.StringOutput[joinMap.CurrentDialString.JoinNumber].StringValue)); + + //End All calls + trilist.SetSigFalseAction(joinMap.EndAllCalls.JoinNumber, EndAllCalls); + + //End a specific call, specified by index. Maximum 8 calls supported + for (int i = 0; i < joinMap.EndCallStart.JoinSpan; i++) + { + var callIndex = i; + + trilist.SetSigFalseAction((uint)(joinMap.EndCallStart.JoinNumber + i), () => + { + + if (callIndex < 0 || callIndex >= ActiveCalls.Count) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Cannot end call. No call found at index: {0}", callIndex); + return; + } + + var call = ActiveCalls[callIndex]; + if (call != null) + { + EndCall(call); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "[End Call] Unable to find call at index '{0}'", i); + } + }); + } + + trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); + + CallStatusChange += (sender, args) => + { + trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); + + Debug.LogMessage(LogEventLevel.Debug, this, "Call Direction: {0}", args.CallItem.Direction); + Debug.LogMessage(LogEventLevel.Debug, this, "Call is incoming: {0}", args.CallItem.Direction == eCodecCallDirection.Incoming); + trilist.SetBool(joinMap.IncomingCall.JoinNumber, args.CallItem.Direction == eCodecCallDirection.Incoming && args.CallItem.Status == eCodecCallStatus.Ringing); + + if (args.CallItem.Direction == eCodecCallDirection.Incoming) + { + trilist.SetSigFalseAction(joinMap.IncomingAnswer.JoinNumber, () => AcceptCall(args.CallItem)); + trilist.SetSigFalseAction(joinMap.IncomingReject.JoinNumber, () => RejectCall(args.CallItem)); + trilist.SetString(joinMap.IncomingCallName.JoinNumber, args.CallItem.Name); + trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, args.CallItem.Number); + } + else + { + trilist.SetString(joinMap.IncomingCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.IncomingCallNumber.JoinNumber, string.Empty); + } - trilist.OnlineStatusChange += (device, args) => + trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); + + trilist.SetUshort(joinMap.ConnectedCallCount.JoinNumber, (ushort)ActiveCalls.Count); + }; + + if (this is IJoinCalls joinCodec) + { + trilist.SetSigFalseAction(joinMap.JoinAllCalls.JoinNumber, () => joinCodec.JoinAllCalls()); + + for (int i = 0; i < joinMap.JoinCallStart.JoinSpan; i++) + { + trilist.SetSigFalseAction((uint)(joinMap.JoinCallStart.JoinNumber + i), () => + { + var call = ActiveCalls[i]; + if (call != null) + { + joinCodec.JoinCall(call); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "[Join Call] Unable to find call at index '{0}'", i); + } + }); + } + } + + if (this is IHasCallHold holdCodec) + { + trilist.SetSigFalseAction(joinMap.HoldAllCalls.JoinNumber, () => + { + foreach (var call in ActiveCalls) + { + holdCodec.HoldCall(call); + } + }); + + for (int i = 0; i < joinMap.HoldCallsStart.JoinSpan; i++) + { + var index = i; + + trilist.SetSigFalseAction((uint)(joinMap.HoldCallsStart.JoinNumber + index), () => + { + if (index < 0 || index >= ActiveCalls.Count) return; + + var call = ActiveCalls[index]; + if (call != null) + { + holdCodec.HoldCall(call); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "[Hold Call] Unable to find call at index '{0}'", i); + } + }); + + trilist.SetSigFalseAction((uint)(joinMap.ResumeCallsStart.JoinNumber + index), () => + { + if (index < 0 || index >= ActiveCalls.Count) return; + + var call = ActiveCalls[index]; + if (call != null) + { + holdCodec.ResumeCall(call); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "[Resume Call] Unable to find call at index '{0}'", i); + } + }); + } + } + + + + trilist.OnlineStatusChange += (device, args) => { if (!args.DeviceOnLine) return; + // TODO [ ] #983 Debug.LogMessage(LogEventLevel.Information, this, "LinkVideoCodecCallControlsToApi: device is {0}, IsInCall {1}", args.DeviceOnLine ? "online" : "offline", IsInCall); trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); trilist.SetString(joinMap.CurrentCallData.JoinNumber, "\xFC"); trilist.SetString(joinMap.CurrentCallData.JoinNumber, UpdateCallStatusXSig()); }; - } - private string UpdateCallStatusXSig() - { - const int maxCalls = 8; - const int maxStrings = 6; - const int maxDigitals = 2; - const int offset = maxStrings + maxDigitals; - var stringIndex = 0; - var digitalIndex = maxStrings * maxCalls; - var arrayIndex = 0; - - var tokenArray = new XSigToken[maxCalls * offset]; //set array size for number of calls * pieces of info - - foreach (var call in ActiveCalls) - { - if (arrayIndex >= maxCalls * offset) - break; - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, call.IsActiveCall); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, call.IsOnHold); - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, call.Name ?? String.Empty); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, call.Number ?? String.Empty); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString()); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, call.Type.ToString()); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, call.Status.ToString()); - - // May need to verify correct string format here - var dur = string.Format("{0:c}", call.Duration); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, dur); - - arrayIndex += offset; - stringIndex += maxStrings; - digitalIndex += maxDigitals; - } - while (arrayIndex < maxCalls * offset) - { - //digitals - tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); - tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); - - - //serials - tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); - tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); - tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); - tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); - tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); - tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); - - arrayIndex += offset; - stringIndex += maxStrings; - digitalIndex += maxDigitals; } - return GetXSigString(tokenArray); - } - - private void LinkVideoCodecDtmfToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.Dtmf0.JoinNumber, () => SendDtmfAction("0", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf1.JoinNumber, () => SendDtmfAction("1", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf2.JoinNumber, () => SendDtmfAction("2", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf3.JoinNumber, () => SendDtmfAction("3", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf4.JoinNumber, () => SendDtmfAction("4", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf5.JoinNumber, () => SendDtmfAction("5", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf6.JoinNumber, () => SendDtmfAction("6", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf7.JoinNumber, () => SendDtmfAction("7", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf8.JoinNumber, () => SendDtmfAction("8", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.Dtmf9.JoinNumber, () => SendDtmfAction("9", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.DtmfStar.JoinNumber, () => SendDtmfAction("*", trilist, joinMap)); - trilist.SetSigFalseAction(joinMap.DtmfPound.JoinNumber, () => SendDtmfAction("#", trilist, joinMap)); - } - - /// - /// Sends the specified string as a DTMF command. - /// Reads the value of the SendDtmfToSpecificCallInstance digital join and SelectCall analog join to determine - /// Whther to send to a specific call index or to the last connected call - /// - /// - /// - /// - private void SendDtmfAction(string s, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - if (!trilist.GetBool(joinMap.SendDtmfToSpecificCallIndex.JoinNumber)) + private string UpdateCallStatusXSig() { - SendDtmf(s); - } - else - { - var callIndex = trilist.GetUshort(joinMap.SelectCall.JoinNumber); - if (callIndex > 0 && callIndex <= 8) + const int maxCalls = 8; + const int maxStrings = 6; + const int maxDigitals = 2; + const int offset = maxStrings + maxDigitals; + var stringIndex = 0; + var digitalIndex = maxStrings * maxCalls; + var arrayIndex = 0; + + var tokenArray = new XSigToken[maxCalls * offset]; //set array size for number of calls * pieces of info + + foreach (var call in ActiveCalls) { - var call = ActiveCalls[callIndex - 1]; - if (call != null && call.IsActiveCall) + if (arrayIndex >= maxCalls * offset) + break; + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, call.IsActiveCall); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, call.IsOnHold); + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, call.Name ?? String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, call.Number ?? String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString()); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, call.Type.ToString()); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, call.Status.ToString()); + if (call.Duration != null) { - SendDtmf(s, call); - } - else - { - Debug.LogMessage(LogEventLevel.Information, this, "Warning: No call found at index {0} or call is not active.", callIndex); + // May need to verify correct string format here + var dur = string.Format("{0:c}", call.Duration); + tokenArray[arrayIndex + 6] = new XSigSerialToken(stringIndex + 6, dur); } + + arrayIndex += offset; + stringIndex += maxStrings; + digitalIndex += maxDigitals; + } + while (arrayIndex < maxCalls * offset) + { + //digitals + tokenArray[digitalIndex] = new XSigDigitalToken(digitalIndex + 1, false); + tokenArray[digitalIndex + 1] = new XSigDigitalToken(digitalIndex + 2, false); + + + //serials + tokenArray[stringIndex] = new XSigSerialToken(stringIndex + 1, String.Empty); + tokenArray[stringIndex + 1] = new XSigSerialToken(stringIndex + 2, String.Empty); + tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, String.Empty); + tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, String.Empty); + tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, String.Empty); + tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, String.Empty); + + arrayIndex += offset; + stringIndex += maxStrings; + digitalIndex += maxDigitals; + } + + return GetXSigString(tokenArray); + } + + private void LinkVideoCodecDtmfToApi(BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.Dtmf0.JoinNumber, () => SendDtmfAction("0", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf1.JoinNumber, () => SendDtmfAction("1", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf2.JoinNumber, () => SendDtmfAction("2", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf3.JoinNumber, () => SendDtmfAction("3", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf4.JoinNumber, () => SendDtmfAction("4", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf5.JoinNumber, () => SendDtmfAction("5", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf6.JoinNumber, () => SendDtmfAction("6", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf7.JoinNumber, () => SendDtmfAction("7", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf8.JoinNumber, () => SendDtmfAction("8", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.Dtmf9.JoinNumber, () => SendDtmfAction("9", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.DtmfStar.JoinNumber, () => SendDtmfAction("*", trilist, joinMap)); + trilist.SetSigFalseAction(joinMap.DtmfPound.JoinNumber, () => SendDtmfAction("#", trilist, joinMap)); + } + + /// + /// Sends the specified string as a DTMF command. + /// Reads the value of the SendDtmfToSpecificCallInstance digital join and SelectCall analog join to determine + /// Whther to send to a specific call index or to the last connected call + /// + /// + /// + /// + private void SendDtmfAction(string s, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + if (!trilist.GetBool(joinMap.SendDtmfToSpecificCallIndex.JoinNumber)) + { + SendDtmf(s); } else { - Debug.LogMessage(LogEventLevel.Information, this, "Warning: Invalid call index specified. Please use a value of 1-8."); + var callIndex = trilist.GetUshort(joinMap.SelectCall.JoinNumber); + if (callIndex > 0 && callIndex <= 8) + { + var call = ActiveCalls[callIndex - 1]; + if (call != null && call.IsActiveCall) + { + SendDtmf(s, call); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Warning: No call found at index {0} or call is not active.", callIndex); + } + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Warning: Invalid call index specified. Please use a value of 1-8."); + } } } - } - private void LinkVideoCodecCameraLayoutsToApi(IHasCodecLayouts codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.CameraLayout.JoinNumber, codec.LocalLayoutToggle); - - codec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentLayoutStringFb.JoinNumber]); - } - - private void LinkVideoCodecCameraModeToApi(IHasCameraAutoMode codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.CameraModeAuto.JoinNumber, codec.CameraAutoModeOn); - trilist.SetSigFalseAction(joinMap.CameraModeManual.JoinNumber, codec.CameraAutoModeOff); - - codec.CameraAutoModeIsOnFeedback.OutputChange += (o, a) => + private void LinkVideoCodecCameraLayoutsToApi(IHasCodecLayouts codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - if (codec is IHasCameraOff offCodec) + trilist.SetSigFalseAction(joinMap.CameraLayout.JoinNumber, codec.LocalLayoutToggle); + + codec.LocalLayoutFeedback.LinkInputSig(trilist.StringInput[joinMap.CurrentLayoutStringFb.JoinNumber]); + } + + private void LinkVideoCodecCameraModeToApi(IHasCameraAutoMode codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + trilist.SetSigFalseAction(joinMap.CameraModeAuto.JoinNumber, codec.CameraAutoModeOn); + trilist.SetSigFalseAction(joinMap.CameraModeManual.JoinNumber, codec.CameraAutoModeOff); + + codec.CameraAutoModeIsOnFeedback.OutputChange += (o, a) => { - if (offCodec.CameraIsOffFeedback.BoolValue) + if (codec is IHasCameraOff offCodec) + { + if (offCodec.CameraIsOffFeedback.BoolValue) + { + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, false); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); + trilist.SetBool(joinMap.CameraModeOff.JoinNumber, true); + return; + } + + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, a.BoolValue); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !a.BoolValue); + trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); + return; + } + + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, a.BoolValue); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !a.BoolValue); + trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); + }; + + + if (codec is IHasCameraOff offModeCodec) + { + if (offModeCodec.CameraIsOffFeedback.BoolValue) { trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, false); trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); @@ -1651,382 +1684,360 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingSource, return; } - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, a.BoolValue); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !a.BoolValue); + trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, codec.CameraAutoModeIsOnFeedback.BoolValue); + trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !codec.CameraAutoModeIsOnFeedback.BoolValue); trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); return; } - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, a.BoolValue); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !a.BoolValue); - trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); - }; - - - if (codec is IHasCameraOff offModeCodec) - { - if (offModeCodec.CameraIsOffFeedback.BoolValue) - { - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, false); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, false); - trilist.SetBool(joinMap.CameraModeOff.JoinNumber, true); - return; - } - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, codec.CameraAutoModeIsOnFeedback.BoolValue); trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !codec.CameraAutoModeIsOnFeedback.BoolValue); trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); - return; } - trilist.SetBool(joinMap.CameraModeAuto.JoinNumber, codec.CameraAutoModeIsOnFeedback.BoolValue); - trilist.SetBool(joinMap.CameraModeManual.JoinNumber, !codec.CameraAutoModeIsOnFeedback.BoolValue); - trilist.SetBool(joinMap.CameraModeOff.JoinNumber, false); - } - - private void LinkVideoCodecSelfviewToApi(IHasCodecSelfView codec, BasicTriList trilist, - VideoCodecControllerJoinMap joinMap) - { - trilist.SetSigFalseAction(joinMap.CameraSelfView.JoinNumber, codec.SelfViewModeToggle); - - codec.SelfviewIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.CameraSelfView.JoinNumber]); - } - - private void LinkVideoCodecCameraToApi(IHasCodecCameras codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - //Camera PTZ - trilist.SetBoolSigAction(joinMap.CameraTiltUp.JoinNumber, (b) => + private void LinkVideoCodecSelfviewToApi(IHasCodecSelfView codec, BasicTriList trilist, + VideoCodecControllerJoinMap joinMap) { - if (codec.SelectedCamera == null) return; + trilist.SetSigFalseAction(joinMap.CameraSelfView.JoinNumber, codec.SelfViewModeToggle); - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.TiltUp(); - else camera.TiltStop(); - }); - - trilist.SetBoolSigAction(joinMap.CameraTiltDown.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.TiltDown(); - else camera.TiltStop(); - }); - trilist.SetBoolSigAction(joinMap.CameraPanLeft.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.PanLeft(); - else camera.PanStop(); - }); - trilist.SetBoolSigAction(joinMap.CameraPanRight.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.PanRight(); - else camera.PanStop(); - }); - - trilist.SetBoolSigAction(joinMap.CameraZoomIn.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.ZoomIn(); - else camera.ZoomStop(); - }); - - trilist.SetBoolSigAction(joinMap.CameraZoomOut.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - - if (b) camera.ZoomOut(); - else camera.ZoomStop(); - }); - - - trilist.SetBoolSigAction(joinMap.CameraFocusNear.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; - - if (b) camera.FocusNear(); - else camera.FocusStop(); - }); - - trilist.SetBoolSigAction(joinMap.CameraFocusFar.JoinNumber, (b) => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; - - if (b) camera.FocusFar(); - else camera.FocusStop(); - }); - - trilist.SetSigFalseAction(joinMap.CameraFocusAuto.JoinNumber, () => - { - if (codec.SelectedCamera == null) return; - - if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; - - camera.TriggerAutoFocus(); - }); - - // Camera count - trilist.SetUshort(joinMap.CameraCount.JoinNumber, (ushort)codec.Cameras.Count); - - // Camera names - for (uint i = 0; i < joinMap.CameraNamesFb.JoinSpan; i++) - { - //Check the count first - if (i < codec.Cameras.Count && codec.Cameras[(int)i] != null) - { - trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, codec.Cameras[(int)i].Name); - } - else - { - trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, ""); - } + codec.SelfviewIsOnFeedback.LinkInputSig(trilist.BooleanInput[joinMap.CameraSelfView.JoinNumber]); } - //Camera Select - trilist.SetUShortSigAction(joinMap.CameraNumberSelect.JoinNumber, (i) => + private void LinkVideoCodecCameraToApi(IHasCodecCameras codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) { - if (i > 0 && i <= codec.Cameras.Count) + //Camera PTZ + trilist.SetBoolSigAction(joinMap.CameraTiltUp.JoinNumber, (b) => { - codec.SelectCamera(codec.Cameras[i - 1].Key); - } - else + if (codec.SelectedCamera == null) return; + + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; + + if (b) camera.TiltUp(); + else camera.TiltStop(); + }); + + trilist.SetBoolSigAction(joinMap.CameraTiltDown.JoinNumber, (b) => { - Debug.LogMessage(LogEventLevel.Information, this, "Unable to select. No camera found at index {0}", i); - } - }); + if (codec.SelectedCamera == null) return; - // Set initial selected camera feedback - if (codec.SelectedCamera != null) - { - trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)codec.Cameras.FindIndex((c) => c.Key == codec.SelectedCamera.Key)); - } + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - codec.CameraSelected += (sender, args) => - { - var i = (ushort)codec.Cameras.FindIndex((c) => c.Key == args.SelectedCamera.Key); - - trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)(i + 1)); - - if (codec is IHasCodecRoomPresets) + if (b) camera.TiltDown(); + else camera.TiltStop(); + }); + trilist.SetBoolSigAction(joinMap.CameraPanLeft.JoinNumber, (b) => { - return; - } + if (codec.SelectedCamera == null) return; - if (!(args.SelectedCamera is IHasCameraPresets)) + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; + + if (b) camera.PanLeft(); + else camera.PanStop(); + }); + trilist.SetBoolSigAction(joinMap.CameraPanRight.JoinNumber, (b) => { - return; - } + if (codec.SelectedCamera == null) return; - var cam = args.SelectedCamera as IHasCameraPresets; - SetCameraPresetNames(cam.Presets); + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; - (args.SelectedCamera as IHasCameraPresets).PresetsListHasChanged += (o, eventArgs) => SetCameraPresetNames(cam.Presets); + if (b) camera.PanRight(); + else camera.PanStop(); + }); - trilist.SetUShortSigAction(joinMap.CameraPresetSelect.JoinNumber, - (a) => - { - cam.PresetSelect(a); - trilist.SetUshort(joinMap.CameraPresetSelect.JoinNumber, a); - }); + trilist.SetBoolSigAction(joinMap.CameraZoomIn.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; - trilist.SetSigFalseAction(joinMap.CameraPresetSave.JoinNumber, - () => + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; + + if (b) camera.ZoomIn(); + else camera.ZoomStop(); + }); + + trilist.SetBoolSigAction(joinMap.CameraZoomOut.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; + + if (!(codec.SelectedCamera is IHasCameraPtzControl camera)) return; + + if (b) camera.ZoomOut(); + else camera.ZoomStop(); + }); + + + trilist.SetBoolSigAction(joinMap.CameraFocusNear.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; + + if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; + + if (b) camera.FocusNear(); + else camera.FocusStop(); + }); + + trilist.SetBoolSigAction(joinMap.CameraFocusFar.JoinNumber, (b) => + { + if (codec.SelectedCamera == null) return; + + if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; + + if (b) camera.FocusFar(); + else camera.FocusStop(); + }); + + trilist.SetSigFalseAction(joinMap.CameraFocusAuto.JoinNumber, () => + { + if (codec.SelectedCamera == null) return; + + if (!(codec.SelectedCamera is IHasCameraFocusControl camera)) return; + + camera.TriggerAutoFocus(); + }); + + // Camera count + trilist.SetUshort(joinMap.CameraCount.JoinNumber, (ushort)codec.Cameras.Count); + + // Camera names + for (uint i = 0; i < joinMap.CameraNamesFb.JoinSpan; i++) + { + //Check the count first + if (i < codec.Cameras.Count && codec.Cameras[(int)i] != null) { - cam.PresetStore(trilist.UShortOutput[joinMap.CameraPresetSelect.JoinNumber].UShortValue, - String.Empty); - trilist.PulseBool(joinMap.CameraPresetSave.JoinNumber, 3000); - }); - }; - - if (!(codec is IHasCodecRoomPresets)) return; - - var presetCodec = codec as IHasCodecRoomPresets; - - presetCodec.CodecRoomPresetsListHasChanged += - (sender, args) => SetCameraPresetNames(presetCodec.NearEndPresets); - - //Camera Presets - trilist.SetUShortSigAction(joinMap.CameraPresetSelect.JoinNumber, (i) => - { - presetCodec.CodecRoomPresetSelect(i); - }); - - - // Far End Presets - trilist.SetUShortSigAction(joinMap.FarEndPresetSelect.JoinNumber, (i) => - { - presetCodec.SelectFarEndPreset(i); - }); - - - trilist.SetSigFalseAction(joinMap.CameraPresetSave.JoinNumber, - () => - { - presetCodec.CodecRoomPresetStore( - trilist.UShortOutput[joinMap.CameraPresetSelect.JoinNumber].UShortValue, String.Empty); - trilist.PulseBool(joinMap.CameraPresetSave.JoinNumber, 3000); - }); - - trilist.OnlineStatusChange += (device, args) => - { - if (!args.DeviceOnLine) return; - - trilist.SetString(joinMap.CameraPresetNames.JoinNumber, "\xFC"); - SetCameraPresetNames(presetCodec.NearEndPresets); - }; - } - - // Following fields only used for Bridging - private int _selectedRecentCallItemIndex; - private DirectoryItem _selectedDirectoryItem; - - private void LinkVideoCodecCallHistoryToApi(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - codec.CallHistory.RecentCallsListHasChanged += (o, a) => - { - UpdateCallHistory(codec, trilist, joinMap); - }; - - // Selected item action and feedback - trilist.SetUShortSigAction(joinMap.SelectRecentCallItem.JoinNumber, (u) => - { - if (u == 0 || u > codec.CallHistory.RecentCalls.Count) - { - Debug.LogMessage(LogEventLevel.Verbose, this, "Recent Call History index out of range"); - return; - } - - _selectedRecentCallItemIndex = (int)(u - 1); - trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, u); - - var _selectedRecentCallItem = codec.CallHistory.RecentCalls[_selectedRecentCallItemIndex]; - - if (_selectedRecentCallItem != null) - { - trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, _selectedRecentCallItem.Name); - trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, _selectedRecentCallItem.Number); - trilist.SetSigFalseAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber, () => codec.RemoveCallHistoryEntry(_selectedRecentCallItem)); - trilist.SetSigFalseAction(joinMap.DialSelectedRecentCallItem.JoinNumber, () => this.Dial(_selectedRecentCallItem.Number)); + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, codec.Cameras[(int)i].Name); } else { - trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); - trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); - trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); - trilist.ClearBoolSigAction(joinMap.DialSelectedRecentCallItem.JoinNumber); + trilist.SetString(joinMap.CameraNamesFb.JoinNumber + i, ""); } - }); - } - - - - private void UpdateCallHistory(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) - { - // Clear out selected item - _selectedRecentCallItemIndex = 0; - - trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, 0); - trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); - trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); - trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); - // - - trilist.SetUshort(joinMap.RecentCallCount.JoinNumber, (ushort)codec.CallHistory.RecentCalls.Count); - - // Update the call history joins - var maxItems = joinMap.RecentCallNamesStart.JoinSpan; - - // Create history - uint index = 0; - for (uint i = 0; i < maxItems && i < codec.CallHistory.RecentCalls.Count; i++) - { - trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].Name); - trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].StartTime.ToShortTimeString()); - trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)codec.CallHistory.RecentCalls[(int)i].OccurrenceType); - //i++; - index = i; - } - - //foreach(var item in codec.CallHistory.RecentCalls) - //{ - // trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, item.Name); - // trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, item.StartTime.ToShortTimeString()); - // trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)item.OccurrenceType); - // i++; - //} - - // Clears existing items - for (uint j = index; j < maxItems; j++) - { - trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + j, string.Empty); - trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + j, string.Empty); - trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + j, 0); - } - } - - private string SetCameraPresetNames(IEnumerable presets) - { - return SetCameraPresetNames(presets.Select(p => p.Description).ToList()); - } - - private string SetCameraPresetNames(IEnumerable presets) - { - return SetCameraPresetNames(presets.Select(p => p.Description).ToList()); - } - - private string SetCameraPresetNames(ICollection presets) - { - var i = 1; //start index for xsig; - - var tokenArray = new XSigToken[presets.Count]; - - foreach (var preset in presets) - { - var cameraPreset = new XSigSerialToken(i, preset); - tokenArray[i - 1] = cameraPreset; - i++; - } - - return GetXSigString(tokenArray); - } - - private string GetXSigString(XSigToken[] tokenArray) - { - string returnString; - using (var s = new MemoryStream()) - { - using (var tw = new XSigTokenStreamWriter(s, true)) - { - tw.WriteXSigData(tokenArray); } - var xSig = s.ToArray(); + //Camera Select + trilist.SetUShortSigAction(joinMap.CameraNumberSelect.JoinNumber, (i) => + { + if (i > 0 && i <= codec.Cameras.Count) + { + codec.SelectCamera(codec.Cameras[i - 1].Key); + } + else + { + Debug.LogMessage(LogEventLevel.Information, this, "Unable to select. No camera found at index {0}", i); + } + }); - returnString = Encoding.GetEncoding(XSigEncoding).GetString(xSig, 0, xSig.Length); + // Set initial selected camera feedback + if (codec.SelectedCamera != null) + { + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)codec.Cameras.FindIndex((c) => c.Key == codec.SelectedCamera.Key)); + } + + codec.CameraSelected += (sender, args) => + { + var i = (ushort)codec.Cameras.FindIndex((c) => c.Key == args.SelectedCamera.Key); + + trilist.SetUshort(joinMap.CameraNumberSelect.JoinNumber, (ushort)(i + 1)); + + if (codec is IHasCodecRoomPresets) + { + return; + } + + if (!(args.SelectedCamera is IHasCameraPresets)) + { + return; + } + + var cam = args.SelectedCamera as IHasCameraPresets; + SetCameraPresetNames(cam.Presets); + + (args.SelectedCamera as IHasCameraPresets).PresetsListHasChanged += (o, eventArgs) => SetCameraPresetNames(cam.Presets); + + trilist.SetUShortSigAction(joinMap.CameraPresetSelect.JoinNumber, + (a) => + { + cam.PresetSelect(a); + trilist.SetUshort(joinMap.CameraPresetSelect.JoinNumber, a); + }); + + trilist.SetSigFalseAction(joinMap.CameraPresetSave.JoinNumber, + () => + { + cam.PresetStore(trilist.UShortOutput[joinMap.CameraPresetSelect.JoinNumber].UShortValue, + String.Empty); + trilist.PulseBool(joinMap.CameraPresetSave.JoinNumber, 3000); + }); + }; + + if (!(codec is IHasCodecRoomPresets)) return; + + var presetCodec = codec as IHasCodecRoomPresets; + + presetCodec.CodecRoomPresetsListHasChanged += + (sender, args) => SetCameraPresetNames(presetCodec.NearEndPresets); + + //Camera Presets + trilist.SetUShortSigAction(joinMap.CameraPresetSelect.JoinNumber, (i) => + { + presetCodec.CodecRoomPresetSelect(i); + }); + + + // Far End Presets + trilist.SetUShortSigAction(joinMap.FarEndPresetSelect.JoinNumber, (i) => + { + presetCodec.SelectFarEndPreset(i); + }); + + + trilist.SetSigFalseAction(joinMap.CameraPresetSave.JoinNumber, + () => + { + presetCodec.CodecRoomPresetStore( + trilist.UShortOutput[joinMap.CameraPresetSelect.JoinNumber].UShortValue, String.Empty); + trilist.PulseBool(joinMap.CameraPresetSave.JoinNumber, 3000); + }); + + trilist.OnlineStatusChange += (device, args) => + { + if (!args.DeviceOnLine) return; + + // TODO [ ] Issue #868 + trilist.SetString(joinMap.CameraPresetNames.JoinNumber, "\xFC"); + SetCameraPresetNames(presetCodec.NearEndPresets); + }; } - return returnString; + // Following fields only used for Bridging + private int _selectedRecentCallItemIndex; + private DirectoryItem _selectedDirectoryItem; + + private void LinkVideoCodecCallHistoryToApi(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + codec.CallHistory.RecentCallsListHasChanged += (o, a) => + { + UpdateCallHistory(codec, trilist, joinMap); + }; + + // Selected item action and feedback + trilist.SetUShortSigAction(joinMap.SelectRecentCallItem.JoinNumber, (u) => + { + if (u == 0 || u > codec.CallHistory.RecentCalls.Count) + { + Debug.LogMessage(LogEventLevel.Verbose, this, "Recent Call History index out of range"); + return; + } + + _selectedRecentCallItemIndex = (int)(u - 1); + trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, u); + + var _selectedRecentCallItem = codec.CallHistory.RecentCalls[_selectedRecentCallItemIndex]; + + if (_selectedRecentCallItem != null) + { + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, _selectedRecentCallItem.Name); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, _selectedRecentCallItem.Number); + trilist.SetSigFalseAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber, () => codec.RemoveCallHistoryEntry(_selectedRecentCallItem)); + trilist.SetSigFalseAction(joinMap.DialSelectedRecentCallItem.JoinNumber, () => this.Dial(_selectedRecentCallItem.Number)); + } + else + { + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); + trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); + trilist.ClearBoolSigAction(joinMap.DialSelectedRecentCallItem.JoinNumber); + } + }); + } + + + + private void UpdateCallHistory(IHasCallHistory codec, BasicTriList trilist, VideoCodecControllerJoinMap joinMap) + { + // Clear out selected item + _selectedRecentCallItemIndex = 0; + + trilist.SetUshort(joinMap.SelectRecentCallItem.JoinNumber, 0); + trilist.SetString(joinMap.SelectedRecentCallName.JoinNumber, string.Empty); + trilist.SetString(joinMap.SelectedRecentCallNumber.JoinNumber, string.Empty); + trilist.ClearBoolSigAction(joinMap.RemoveSelectedRecentCallItem.JoinNumber); + // + + trilist.SetUshort(joinMap.RecentCallCount.JoinNumber, (ushort)codec.CallHistory.RecentCalls.Count); + + // Update the call history joins + var maxItems = joinMap.RecentCallNamesStart.JoinSpan; + + // Create history + uint index = 0; + for (uint i = 0; i < maxItems && i < codec.CallHistory.RecentCalls.Count; i++) + { + trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].Name); + trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, codec.CallHistory.RecentCalls[(int)i].StartTime.ToShortTimeString()); + trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)codec.CallHistory.RecentCalls[(int)i].OccurrenceType); + //i++; + index = i; + } + + //foreach(var item in codec.CallHistory.RecentCalls) + //{ + // trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + i, item.Name); + // trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + i, item.StartTime.ToShortTimeString()); + // trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + i, (ushort)item.OccurrenceType); + // i++; + //} + + // Clears existing items + for (uint j = index; j < maxItems; j++) + { + trilist.SetString(joinMap.RecentCallNamesStart.JoinNumber + j, string.Empty); + trilist.SetString(joinMap.RecentCallTimesStart.JoinNumber + j, string.Empty); + trilist.SetUshort(joinMap.RecentCallOccurrenceType.JoinNumber + j, 0); + } + } + + private string SetCameraPresetNames(IEnumerable presets) + { + return SetCameraPresetNames(presets.Select(p => p.Description).ToList()); + } + + private string SetCameraPresetNames(IEnumerable presets) + { + return SetCameraPresetNames(presets.Select(p => p.Description).ToList()); + } + + private string SetCameraPresetNames(ICollection presets) + { + var i = 1; //start index for xsig; + + var tokenArray = new XSigToken[presets.Count]; + + foreach (var preset in presets) + { + var cameraPreset = new XSigSerialToken(i, preset); + tokenArray[i - 1] = cameraPreset; + i++; + } + + return GetXSigString(tokenArray); + } + + private string GetXSigString(XSigToken[] tokenArray) + { + string returnString; + using (var s = new MemoryStream()) + { + using (var tw = new XSigTokenStreamWriter(s, true)) + { + tw.WriteXSigData(tokenArray); + } + + var xSig = s.ToArray(); + + returnString = Encoding.GetEncoding(XSigEncoding).GetString(xSig, 0, xSig.Length); + } + + return returnString; + } + + #endregion } - - #endregion -} - - +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/ContentTypes.cs b/src/PepperDash.Essentials.MobileControl.Messengers/ContentTypes.cs index 67aea972..ab60d3f7 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/ContentTypes.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/ContentTypes.cs @@ -8,16 +8,16 @@ namespace PepperDash.Essentials.AppServer /// public class SourceSelectMessageContent { + + [JsonProperty("sourceListItemKey")] /// /// Gets or sets the SourceListItemKey /// - [JsonProperty("sourceListItemKey")] public string SourceListItemKey { get; set; } + [JsonProperty("sourceListKey")] /// /// Gets or sets the SourceListKey /// - [JsonProperty("sourceListKey")] - public string SourceListKey { get; set; } } @@ -27,20 +27,20 @@ namespace PepperDash.Essentials.AppServer public class DirectRoute { + [JsonProperty("sourceKey")] /// /// Gets or sets the SourceKey /// - [JsonProperty("sourceKey")] public string SourceKey { get; set; } + [JsonProperty("destinationKey")] /// /// Gets or sets the DestinationKey /// - [JsonProperty("destinationKey")] public string DestinationKey { get; set; } + [JsonProperty("signalType")] /// /// Gets or sets the SignalType /// - [JsonProperty("signalType")] public eRoutingSignalType SignalType { get; set; } } @@ -52,4 +52,4 @@ namespace PepperDash.Essentials.AppServer /// Delegate for PressAndHoldAction /// public delegate void PressAndHoldAction(bool b); -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IRoutingSinkWithFeedbackMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/DisplayBaseMessenger.cs similarity index 74% rename from src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IRoutingSinkWithFeedbackMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/DisplayBaseMessenger.cs index d2883903..349b8d58 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IRoutingSinkWithFeedbackMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/DisplayBaseMessenger.cs @@ -3,8 +3,6 @@ using PepperDash.Core; using PepperDash.Core.Logging; using PepperDash.Essentials.AppServer; using PepperDash.Essentials.AppServer.Messengers; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Routing; using System.Linq; using DisplayBase = PepperDash.Essentials.Devices.Common.Displays.DisplayBase; @@ -13,22 +11,15 @@ namespace PepperDash.Essentials.Room.MobileControl /// /// Represents a DisplayBaseMessenger /// - public class IRoutingSinkWithFeedbackMessenger : MessengerBase + public class DisplayBaseMessenger : MessengerBase { - private readonly IRoutingSinkWithFeedback display; + private readonly DisplayBase display; - /// - /// Create an instance of the class. - /// - /// - /// - /// - public IRoutingSinkWithFeedbackMessenger(string key, string messagePath, IRoutingSinkWithFeedback device) : base(key, messagePath, device) + public DisplayBaseMessenger(string key, string messagePath, DisplayBase device) : base(key, messagePath, device) { display = device; } - /// protected override void RegisterActions() { base.RegisterActions(); @@ -69,4 +60,4 @@ namespace PepperDash.Essentials.Room.MobileControl }); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IChannelMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IChannelMessenger.cs index 8d185804..505fd214 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IChannelMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IChannelMessenger.cs @@ -11,18 +11,11 @@ namespace PepperDash.Essentials.Room.MobileControl { private readonly IChannel channelDevice; - /// - /// Create an instance of the class. - /// - /// - /// - /// public IChannelMessenger(string key, string messagePath, IChannel device) : base(key, messagePath, device as IKeyName) { channelDevice = device; } - /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IColorMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IColorMessenger.cs index 2a50c4a8..cef6111f 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IColorMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IColorMessenger.cs @@ -10,19 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class IColorMessenger : MessengerBase { private readonly IColor colorDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public IColorMessenger(string key, string messagePath, IColor device) : base(key, messagePath, device as IKeyName) { colorDevice = device as IColor; } - /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDPadMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDPadMessenger.cs index d7207e09..92c21a0b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDPadMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDPadMessenger.cs @@ -10,19 +10,12 @@ namespace PepperDash.Essentials.Room.MobileControl public class IDPadMessenger : MessengerBase { private readonly IDPad dpadDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public IDPadMessenger(string key, string messagePath, IDPad device) : base(key, messagePath, device as IKeyName) { dpadDevice = device; } - /// + protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDvrMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDvrMessenger.cs index 08b1d100..f0ae426c 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDvrMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IDvrMessenger.cs @@ -1,4 +1,3 @@ - using PepperDash.Core; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; @@ -11,20 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class IDvrMessenger : MessengerBase { private readonly IDvr dvrDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public IDvrMessenger(string key, string messagePath, IDvr device) : base(key, messagePath, device as IKeyName) { dvrDevice = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -34,4 +24,4 @@ namespace PepperDash.Essentials.Room.MobileControl } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IHasPowerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IHasPowerMessenger.cs index 247b533d..864c20bf 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IHasPowerMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/IHasPowerMessenger.cs @@ -10,20 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class IHasPowerMessenger : MessengerBase { private readonly IHasPowerControl powerDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public IHasPowerMessenger(string key, string messagePath, IHasPowerControl device) : base(key, messagePath, device as IKeyName) { powerDevice = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/INumericMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/INumericMessenger.cs index 746caf18..d07331d2 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/INumericMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/INumericMessenger.cs @@ -10,20 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class INumericKeypadMessenger : MessengerBase { private readonly INumericKeypad keypadDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public INumericKeypadMessenger(string key, string messagePath, INumericKeypad device) : base(key, messagePath, device as IKeyName) { keypadDevice = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ISetTopBoxControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ISetTopBoxControlsMessenger.cs index 5e5a286d..f87480bd 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ISetTopBoxControlsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ISetTopBoxControlsMessenger.cs @@ -1,4 +1,3 @@ - using PepperDash.Core; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; @@ -11,20 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class ISetTopBoxControlsMessenger : MessengerBase { private readonly ISetTopBoxControls stbDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public ISetTopBoxControlsMessenger(string key, string messagePath, ISetTopBoxControls device) : base(key, messagePath, device as IKeyName) { stbDevice = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -45,11 +35,8 @@ namespace PepperDash.Essentials.Room.MobileControl } } - /// - /// Represents the state of a ISetTopBoxControls device to be sent to mobile clients - /// public class SetTopBoxControlsState : DeviceStateMessageBase { } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ITransportMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ITransportMessenger.cs index 9eba0848..0943fc0b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ITransportMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/DeviceTypeExtensions/ITransportMessenger.cs @@ -1,4 +1,3 @@ - using PepperDash.Core; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; @@ -11,19 +10,11 @@ namespace PepperDash.Essentials.Room.MobileControl public class ITransportMessenger : MessengerBase { private readonly ITransport transportDevice; - - /// - /// Create an instance of the class. - /// - /// - /// - /// public ITransportMessenger(string key, string messagePath, ITransport device) : base(key, messagePath, device as IKeyName) { transportDevice = device; } - /// protected override void RegisterActions() { base.RegisterActions(); @@ -39,4 +30,4 @@ namespace PepperDash.Essentials.Room.MobileControl } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/AudioCodecBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/AudioCodecBaseMessenger.cs index 7eb3d5aa..a1b30341 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/AudioCodecBaseMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/AudioCodecBaseMessenger.cs @@ -111,7 +111,7 @@ namespace PepperDash.Essentials.AppServer.Messengers calls = Codec.ActiveCalls, info = new { - phoneNumber = info?.PhoneNumber + phoneNumber = info.PhoneNumber } }), id ); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraControlMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs similarity index 98% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraControlMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs index 3c02ee59..8c9f0451 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraControlMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CameraBaseMessenger.cs @@ -11,7 +11,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Messenger for a CameraBase device /// - public class CameraControlMessenger : MessengerBase where T : IKeyed + public class CameraBaseMessenger : MessengerBase where T : IKeyed { /// /// Gets or sets the Camera @@ -24,7 +24,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public CameraControlMessenger(string key, T camera, string messagePath) + public CameraBaseMessenger(string key, T camera, string messagePath) : base(key, messagePath, camera as IKeyName) { if (camera == null) diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICurrentSourcesMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs similarity index 70% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICurrentSourcesMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs index 12d1dea6..a7c4614b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICurrentSourcesMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/CurrentSourcesMessenger.cs @@ -12,17 +12,17 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Represents a IHasCurrentSourceInfoMessenger /// - public class ICurrentSourcesMessenger : MessengerBase + public class CurrentSourcesMessenger : MessengerBase { private readonly ICurrentSources sourceDevice; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The key. /// The message path. /// The device. - public ICurrentSourcesMessenger(string key, string messagePath, ICurrentSources device) : base(key, messagePath, device as IKeyName) + public CurrentSourcesMessenger(string key, string messagePath, ICurrentSources device) : base(key, messagePath, device as IKeyName) { sourceDevice = device; } @@ -42,19 +42,22 @@ namespace PepperDash.Essentials.AppServer.Messengers { // need to copy the dictionaries to avoid enumeration issues var currentSourceKeys = sourceDevice.CurrentSourceKeys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var currentSources = sourceDevice.CurrentSources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); PostStatusMessage(JToken.FromObject(new { currentSourceKeys, + currentSources, })); }; } - private void SendCurrentSourceStatus(string id) + private void SendCurrentSourceStatus(string id = null) { var message = new CurrentSourcesStateMessage { CurrentSourceKeys = sourceDevice.CurrentSourceKeys, + CurrentSources = sourceDevice.CurrentSources }; PostStatusMessage(message, id); @@ -66,11 +69,18 @@ namespace PepperDash.Essentials.AppServer.Messengers /// public class CurrentSourcesStateMessage : DeviceStateMessageBase { + /// /// Gets or sets the CurrentSourceKey /// [JsonProperty("currentSourceKeys", NullValueHandling = NullValueHandling.Ignore)] public Dictionary CurrentSourceKeys { get; set; } + + /// + /// Gets or sets the CurrentSource + /// + [JsonProperty("currentSources")] + public Dictionary CurrentSources { get; set; } } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceActionMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceActionMessenger.cs deleted file mode 100644 index 42f95138..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceActionMessenger.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers; - -/// -/// Messenger that accepts generic device actions at /action/executeAction -/// and executes them using DeviceJsonApi.DoDeviceAction -/// -public class DeviceActionMessenger : MessengerBase -{ - public DeviceActionMessenger(string key, string messagePath) - : base(key, messagePath) - { - } - - protected override void RegisterActions() - { - AddAction("/executeAction", (id, content) => - { - var action = content.ToObject(); - - if (action == null) - { - this.LogWarning("Received null DeviceActionWrapper"); - return; - } - - DeviceJsonApi.DoDeviceAction(action); - }); - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDeviceInfoProviderMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceInfoMessenger.cs similarity index 86% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDeviceInfoProviderMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceInfoMessenger.cs index fdf18a55..876bcafe 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDeviceInfoProviderMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceInfoMessenger.cs @@ -10,19 +10,19 @@ namespace PepperDash.Essentials.AppServer.Messengers /// Facilitates communication of device information by providing mechanisms for status updates and device /// information reporting. /// - /// The class integrates with an The class integrates with an to manage device-specific information. It uses a debounce timer to limit the /// frequency of updates, ensuring efficient communication. The timer is initialized with a 1-second interval and /// is disabled by default. This class also subscribes to device information change events and provides actions for /// reporting full device status and triggering updates. - public class IDeviceInfoProviderMessenger : MessengerBase + public class DeviceInfoMessenger : MessengerBase { private readonly IDeviceInfoProvider _deviceInfoProvider; private readonly Timer debounceTimer; /// - /// Initializes a new instance of the class, which facilitates communication + /// Initializes a new instance of the class, which facilitates communication /// of device information. /// /// The messenger uses a debounce timer to limit the frequency of certain operations. The @@ -30,8 +30,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// A unique identifier for the messenger instance. /// The path used for sending and receiving messages. /// An implementation of that provides device-specific information. - public IDeviceInfoProviderMessenger(string key, string messagePath, IDeviceInfoProvider device) - : base(key, messagePath, device as Device) + public DeviceInfoMessenger(string key, string messagePath, IDeviceInfoProvider device) : base(key, messagePath, device as Device) { _deviceInfoProvider = device; @@ -95,10 +94,7 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class DeviceInfoStateMessage : DeviceStateMessageBase { - /// - /// Gets or sets the device information associated with this message. This property contains detailed information - /// [JsonProperty("deviceInfo")] public DeviceInfo DeviceInfo { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceMessageBase.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceMessageBase.cs index 54a6ec36..0198df2f 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceMessageBase.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceMessageBase.cs @@ -10,7 +10,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// The device key /// - [JsonProperty("key")] + [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] /// /// Gets or sets the Key /// @@ -19,19 +19,19 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// The device name /// - [JsonProperty("name")] + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; } /// /// The type of the message class /// - [JsonProperty("messageType")] + [JsonProperty("messageType", NullValueHandling = NullValueHandling.Ignore)] public string MessageType => GetType().Name; /// /// Gets or sets the MessageBasePath /// - [JsonProperty("messageBasePath")] + [JsonProperty("messageBasePath", NullValueHandling = NullValueHandling.Ignore)] public string MessageBasePath { get; set; } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITvPresetsProviderMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DevicePresetsModelMessenger.cs similarity index 93% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITvPresetsProviderMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DevicePresetsModelMessenger.cs index fa9bd3b4..833a781b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITvPresetsProviderMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DevicePresetsModelMessenger.cs @@ -10,19 +10,19 @@ using PepperDash.Essentials.Core.Presets; namespace PepperDash.Essentials.AppServer.Messengers { /// - /// Represents a ITvPresetsProviderMessenger + /// Represents a DevicePresetsModelMessenger /// - public class ITvPresetsProviderMessenger : MessengerBase + public class DevicePresetsModelMessenger : MessengerBase { private readonly ITvPresetsProvider _presetsDevice; /// - /// Constructor for ITvPresetsProviderMessenger + /// Constructor for DevicePresetsModelMessenger /// /// The key. /// The message path. /// The presets device. - public ITvPresetsProviderMessenger(string key, string messagePath, ITvPresetsProvider presetsDevice) + public DevicePresetsModelMessenger(string key, string messagePath, ITvPresetsProvider presetsDevice) : base(key, messagePath, presetsDevice as Device) { _presetsDevice = presetsDevice; diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs index 1e538a31..c5c0ab65 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceStateMessageBase.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -8,6 +9,22 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class DeviceStateMessageBase : DeviceMessageBase { + /// + /// The interfaces implmented by the device sending the messsage + /// + [JsonProperty("interfaces", NullValueHandling = NullValueHandling.Ignore)] + [Obsolete("Interfaces are no longer supported and will be removed in a future release. Interfaces for all devices are now retrieved via the /joinroom endpoint in the MobileControlWebsocketServer")] + public List Interfaces { get; private set; } + + /// + /// Sets the interfaces implemented by the device sending the message + /// + /// + [Obsolete("SetInterfaces is no longer supported and will be removed in a future release. Interfaces for all devices are now retrieved via the /joinroom endpoint in the MobileControlWebsocketServer")] + public void SetInterfaces(List interfaces) + { + Interfaces = interfaces; + } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVolumeControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceVolumeMessenger.cs similarity index 84% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVolumeControlsMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceVolumeMessenger.cs index 91b01f78..81df6bac 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IBasicVolumeControlsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/DeviceVolumeMessenger.cs @@ -9,19 +9,19 @@ using PepperDash.Essentials.Core; namespace PepperDash.Essentials.AppServer.Messengers { /// - /// Represents a IBasicVolumeControlsMessenger + /// Represents a DeviceVolumeMessenger /// - public class IBasicVolumeControlsMessenger : MessengerBase + public class DeviceVolumeMessenger : MessengerBase { private readonly IBasicVolumeControls device; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The key. /// The message path. /// The device. - public IBasicVolumeControlsMessenger(string key, string messagePath, IBasicVolumeControls device) + public DeviceVolumeMessenger(string key, string messagePath, IBasicVolumeControls device) : base(key, messagePath, device) { this.device = device; @@ -40,7 +40,6 @@ namespace PepperDash.Essentials.AppServer.Messengers { Volume = new Volume { - Label = device.Name, Level = feedbackDevice?.VolumeLevelFeedback.IntValue ?? -1, Muted = feedbackDevice?.MuteFeedback.BoolValue ?? false, HasMute = true, // assume all devices have mute for now @@ -51,7 +50,6 @@ namespace PepperDash.Essentials.AppServer.Messengers { messageObj.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString(); messageObj.Volume.Units = volumeAdvanced.Units; - messageObj.Volume.MaxLevel = volumeAdvanced.MaxVolumeLevel; } PostStatusMessage(messageObj, id); @@ -157,7 +155,6 @@ namespace PepperDash.Essentials.AppServer.Messengers { message.Volume.RawValue = volumeAdvanced.RawVolumeLevel.ToString(); message.Volume.Units = volumeAdvanced.Units; - message.Volume.MaxLevel = volumeAdvanced.MaxVolumeLevel; } PostStatusMessage(JToken.FromObject(message)); @@ -172,10 +169,10 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class VolumeStateMessage : DeviceStateMessageBase { + [JsonProperty("volume", NullValueHandling = NullValueHandling.Ignore)] /// /// Gets or sets the Volume /// - [JsonProperty("volume", NullValueHandling = NullValueHandling.Ignore)] public Volume Volume { get; set; } } @@ -184,50 +181,29 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class Volume { - /// - /// Gets or sets the Level - /// [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] public int? Level { get; set; } - - /// - /// Gets or sets the HasMute - /// [JsonProperty("hasMute", NullValueHandling = NullValueHandling.Ignore)] public bool? HasMute { get; set; } - - /// - /// Gets or sets the Muted - /// [JsonProperty("muted", NullValueHandling = NullValueHandling.Ignore)] public bool? Muted { get; set; } - + [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] /// /// Gets or sets the Label /// - [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] public string Label { get; set; } + [JsonProperty("rawValue", NullValueHandling = NullValueHandling.Ignore)] /// /// Gets or sets the RawValue /// - [JsonProperty("rawValue", NullValueHandling = NullValueHandling.Ignore)] public string RawValue { get; set; } - /// - /// Gets or sets the Units - /// [JsonConverter(typeof(StringEnumConverter))] [JsonProperty("units", NullValueHandling = NullValueHandling.Ignore)] public eVolumeLevelUnits? Units { get; set; } - - /// - /// Gets or sets the MaxLevel. Defaults to 65535, which is the standard for Crestron devices. This is used by the mobile control app to determine how to scale the volume level for display and input purposes. - /// - [JsonProperty("maxLevel", NullValueHandling = NullValueHandling.Ignore)] - public int MaxLevel { get; set; } = 65535; } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/GenericMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/GenericMessenger.cs index e6a334d3..83def192 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/GenericMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/GenericMessenger.cs @@ -32,4 +32,4 @@ namespace PepperDash.Essentials.AppServer.Messengers PostStatusMessage(state, id); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecInfoMessenger.cs deleted file mode 100644 index 895b5016..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecInfoMessenger.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.AudioCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IAudioCodecInfoMessenger : MessengerBase - { - private readonly IAudioCodecInfo _codecInfo; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public IAudioCodecInfoMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _codecInfo = device as IAudioCodecInfo ?? throw new ArgumentNullException(nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/codecInfoStatus", (id, content) => SendFullStatus(id)); - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IAudioCodecInfoStateMessage - { - PhoneNumber = _codecInfo.CodecInfo?.PhoneNumber - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending audio codec info full status"); - } - } - } - - /// - /// State message for - /// - public class IAudioCodecInfoStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the phone number of the audio codec - /// - [JsonProperty("phoneNumber", NullValueHandling = NullValueHandling.Ignore)] - public string PhoneNumber { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecPhonebookMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecPhonebookMessenger.cs deleted file mode 100644 index 7d336f75..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IAudioCodecPhonebookMessenger.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.AudioCodec; -using System.Collections.Generic; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IAudioCodecPhonebookMessenger : MessengerBase - { - private readonly IAudioCodecPhonebook _phonebook; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public IAudioCodecPhonebookMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _phonebook = device as IAudioCodecPhonebook ?? throw new ArgumentNullException(nameof(device)); - - _phonebook.ListChanged += (sender, args) => SendFullStatus(); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/phonebookStatus", (id, content) => SendFullStatus(id)); - - AddAction("/setEntry", (id, content) => - { - var entry = content.ToObject(); - _phonebook.SetPhonebookEntry(entry.Index, entry.Name, entry.Number); - }); - - AddAction("/dialEntry", (id, content) => - { - var request = content.ToObject>(); - _phonebook.DialPhonebookEntry(request.Value); - }); - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IAudioCodecPhonebookStateMessage - { - PhonebookEntries = _phonebook.PhonebookEntries - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending phonebook full status"); - } - } - } - - /// - /// State message for - /// - public class IAudioCodecPhonebookStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the list of phonebook entries - /// - [JsonProperty("phonebookEntries", NullValueHandling = NullValueHandling.Ignore)] - public List PhonebookEntries { get; set; } - } - - /// - /// Content model for the setEntry action - /// - public class SetPhonebookEntryContent - { - [JsonProperty("index")] - public int Index { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("number")] - public string Number { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICodecCallControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICodecCallControlsMessenger.cs deleted file mode 100644 index 8b70d9ee..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICodecCallControlsMessenger.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class ICodecCallControlsMessenger : MessengerBase - { - private readonly ICodecCallControls _callControls; - - /// Initializes a new instance of the class. - public ICodecCallControlsMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _callControls = device as ICodecCallControls ?? throw new ArgumentNullException(nameof(device)); - _callControls.CallStatusChange += CallControls_CallStatusChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/callControlsStatus", (id, content) => SendFullStatus(id)); - - AddAction("/dialMeeting", (id, content) => - _callControls.Dial(content.ToObject())); - - AddAction("/dialNumber", (id, content) => - { - var message = content.ToObject(); - _callControls.Dial(message.Number, message.Password); - }); - - AddAction("/endCallById", (id, content) => - { - var s = content.ToObject>(); - var call = GetCallWithId(s.Value); - if (call != null) - _callControls.EndCall(call); - }); - - AddAction("/rejectById", (id, content) => - { - var s = content.ToObject>(); - var call = GetCallWithId(s.Value); - if (call != null) - _callControls.RejectCall(call); - }); - - AddAction("/acceptById", (id, content) => - { - var s = content.ToObject>(); - var call = GetCallWithId(s.Value); - if (call != null) - _callControls.AcceptCall(call); - }); - } - - private void CallControls_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - { - try - { - PostStatusMessage(BuildState()); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting call controls status"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - Task.Run(() => PostStatusMessage(BuildState(), id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending call controls full status"); - } - } - - private ICallControlsStateMessage BuildState() - { - return new ICallControlsStateMessage - { - Calls = _callControls.ActiveCalls, - }; - } - - private CodecActiveCallItem GetCallWithId(string id) - { - return _callControls.ActiveCalls?.FirstOrDefault(c => c.Id == id); - } - } - - /// - /// State message for - /// - public class ICallControlsStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the list of active calls. Null if unknown or not applicable. - /// - [JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)] - public List Calls { get; set; } - - } - - public class ICallControlsDialMeetingMessage - { - [JsonProperty("number", Required = Required.Always)] - public string Number { get; set; } - - [JsonProperty("password", NullValueHandling = NullValueHandling.Ignore)] - public string Password { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICommunicationMonitorMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICommunicationMonitorMessenger.cs index e2bd1be5..79622a41 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICommunicationMonitorMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ICommunicationMonitorMessenger.cs @@ -87,4 +87,4 @@ namespace PepperDash.Essentials.AppServer.Messengers } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDialerCallStatusMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDialerCallStatusMessenger.cs deleted file mode 100644 index 4cee7d17..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDialerCallStatusMessenger.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Linq; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Devices.Common.AudioCodec; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for an IDialerCallStatus device - /// - public class IDialerCallStatusMessenger : MessengerBase - { - /// - /// Device being bridged - /// - public IDialerCallStatus Codec { get; private set; } - - /// - /// Constuctor - /// - /// - /// - /// - public IDialerCallStatusMessenger(string key, IDialerCallStatus codec, string messagePath) - : base(key, messagePath, codec as IKeyName) - { - Codec = codec ?? throw new ArgumentNullException("codec"); - codec.CallStatusChange += Codec_CallStatusChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendAtcFullMessageObject(id)); - - AddAction("/audioDialerStatus", (id, content) => SendAtcFullMessageObject(id)); - - AddAction("/dial", (id, content) => - { - var msg = content.ToObject>(); - - Codec.Dial(msg.Value); - }); - - AddAction("/endCallById", (id, content) => - { - var msg = content.ToObject>(); - - var call = GetCallWithId(msg.Value); - if (call != null) - Codec.EndCall(call); - }); - - AddAction("/endAllCalls", (id, content) => Codec.EndAllCalls()); - AddAction("/dtmf", (id, content) => - { - var msg = content.ToObject>(); - - Codec.SendDtmf(msg.Value); - }); - - AddAction("/rejectById", (id, content) => - { - var msg = content.ToObject>(); - - var call = GetCallWithId(msg.Value); - - if (call != null) - Codec.RejectCall(call); - }); - - AddAction("/acceptById", (id, content) => - { - var msg = content.ToObject>(); - var call = GetCallWithId(msg.Value); - if (call != null) - Codec.AcceptCall(call); - }); - } - - /// - /// Helper to grab a call with string ID - /// - /// - /// - private CodecActiveCallItem GetCallWithId(string id) - { - return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id); - } - - private void Codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - { - SendAtcFullMessageObject(); - } - - /// - /// Helper method to build call status for vtc - /// - /// - private void SendAtcFullMessageObject(string id = null) - { - try - { - var info = Codec.CodecInfo; - - PostStatusMessage(JToken.FromObject(new - { - isInCall = Codec.IsInCall, - calls = Codec.ActiveCalls, - info = new - { - phoneNumber = info?.PhoneNumber - } - }), id - ); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending dialer call status full message"); - } - } - } -} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDspPresetsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDspPresetsMessenger.cs index fff90dab..92674574 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDspPresetsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDspPresetsMessenger.cs @@ -12,20 +12,12 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly IDspPresets device; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// public IDspPresetsMessenger(string key, string messagePath, IDspPresets device) : base(key, messagePath, device as IKeyName) { this.device = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -62,11 +54,7 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class IHasDspPresetsStateMessage : DeviceStateMessageBase { - /// - /// Gets or sets the presets. - /// The key is the preset key, and the value is the preset name - /// [JsonProperty("presets")] public Dictionary Presets { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs index f705317e..966d8d77 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IEssentialsRoomCombinerMessenger.cs @@ -214,4 +214,4 @@ namespace PepperDash.Essentials.AppServer.Messengers } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs deleted file mode 100644 index fa746da9..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCallHistoryMessenger.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IHasCallHistoryMessenger : MessengerBase - { - private readonly IHasCallHistory _callHistory; - - /// - /// Initializes a new instance of the class. - /// - public IHasCallHistoryMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _callHistory = device as IHasCallHistory ?? throw new ArgumentException("device must implement IHasCallHistory", nameof(device)); - _callHistory.CallHistory.RecentCallsListHasChanged += CallHistory_RecentCallsListHasChanged; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => PostCallHistory(id)); - AddAction("/callHistoryStatus", (id, content) => PostCallHistory(id)); - - AddAction("/getCallHistory", (id, content) => PostCallHistory(id)); - } - - private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e) - { - try - { - if (!(sender is CodecCallHistory codecCallHistory)) return; - - var recents = codecCallHistory.RecentCalls; - if (recents != null) - { - PostStatusMessage(new IHasCallHistoryStateMessage - { - RecentCalls = recents - }); - } - } - catch (Exception ex) - { - this.LogError(ex, "Error posting call history"); - } - } - - private void PostCallHistory(string id = null) - { - try - { - var recents = _callHistory.CallHistory.RecentCalls; - if (recents != null) - { - PostStatusMessage(new IHasCallHistoryStateMessage - { - RecentCalls = recents, - }, id); - } - } - catch (Exception ex) - { - this.LogError(ex, "Error posting call history"); - } - } - } - - /// - /// State message for - /// - /// - public class IHasCallHistoryStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the list of recent calls. Null if unknown or not applicable. - /// - [JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)] - public List RecentCalls { get; set; } - - /// - /// Gets or sets a value indicating whether the device has call history functionality. Null if unknown or not applicable. - /// - [JsonProperty("hasRecents", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasRecents { get; set; } = true; // Since this messenger should only be used for devices with call history, default to true unless specified otherwise. - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraMuteMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraMuteMessenger.cs deleted file mode 100644 index c1a5340a..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraMuteMessenger.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Cameras; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasCameraMuteMessenger : MessengerBase - { - private readonly IHasCameraMute _cameraMute; - - /// - /// Initializes a new instance of the class. - /// - /// The key for the messenger. - /// The message path for the messenger. - /// The device implementing . - public IHasCameraMuteMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _cameraMute = device as IHasCameraMute ?? throw new ArgumentNullException(nameof(device)); - _cameraMute.CameraIsMutedFeedback.OutputChange += CameraIsMutedFeedback_OutputChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/cameraMuteStatus", (id, content) => SendFullStatus(id)); - - AddAction("/cameraMuteOn", (id, content) => _cameraMute.CameraMuteOn()); - AddAction("/cameraMuteOff", (id, content) => _cameraMute.CameraMuteOff()); - AddAction("/cameraMuteToggle", (id, content) => _cameraMute.CameraMuteToggle()); - } - - private void CameraIsMutedFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - try - { - PostStatusMessage(new IHasCameraMuteStateMessage - { - CameraIsMuted = e.BoolValue - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting camera mute state"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasCameraMuteStateMessage - { - CameraIsMuted = _cameraMute.CameraIsMutedFeedback.BoolValue - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending camera mute full status"); - } - } - } - - /// - /// State message for - /// - public class IHasCameraMuteStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets a value indicating whether the camera is muted. Null if unknown or not applicable. - /// - [JsonProperty("cameraIsMuted", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraIsMuted { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraPresetsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraPresetsMessenger.cs deleted file mode 100644 index b7cc61eb..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCameraPresetsMessenger.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Cameras; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasCameraPresetsMessenger : MessengerBase - { - private readonly IHasCameraPresets _cameraPresets; - - /// - /// Initializes a new instance of the class. - /// - /// The key for the messenger. - /// The message path for the messenger. - /// The device implementing . - public IHasCameraPresetsMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _cameraPresets = device as IHasCameraPresets ?? throw new ArgumentNullException(nameof(device)); - _cameraPresets.PresetsListHasChanged += PresetsListHasChanged; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/presetsStatus", (id, content) => SendFullStatus(id)); - - AddAction("/recallPreset", (id, content) => - { - var msg = content.ToObject>(); - _cameraPresets.PresetSelect(msg.Value); - }); - - AddAction("/savePreset", (id, content) => - { - var msg = content.ToObject>(); - _cameraPresets.PresetStore(msg.Value, string.Empty); - }); - } - - private void PresetsListHasChanged(object sender, EventArgs e) - { - try - { - PostStatusMessage(new IHasCameraPresetsStateMessage - { - Presets = _cameraPresets.Presets - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting camera presets state"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasCameraPresetsStateMessage - { - Presets = _cameraPresets.Presets - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending camera presets full status"); - } - } - } - - /// - /// State message for - /// - public class IHasCameraPresetsStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the list of camera presets. - /// - [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] - public List Presets { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs new file mode 100644 index 00000000..00c2c2fe --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasMessenger.cs @@ -0,0 +1,100 @@ +using Newtonsoft.Json; +using PepperDash.Essentials.Devices.Common.Cameras; +using System; +using System.Collections.Generic; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Messenger for devices that implement the IHasCameras interface. + /// + [Obsolete("Use IHasCamerasWithControlsMessenger instead. This class will be removed in a future version")] + public class IHasCamerasMessenger : MessengerBase + { + /// + /// Device being bridged that implements IHasCameras interface. + /// + public IHasCameras CameraController { get; private set; } + + /// + /// Messenger for devices that implement IHasCameras interface. + /// + /// + /// + /// + /// + public IHasCamerasMessenger(string key, string messagePath, IHasCameras cameraController) + : base(key, messagePath, cameraController) + { + CameraController = cameraController ?? throw new ArgumentNullException("cameraController"); + CameraController.CameraSelected += CameraController_CameraSelected; + } + + private void CameraController_CameraSelected(object sender, CameraSelectedEventArgs e) + { + PostStatusMessage(new IHasCamerasStateMessage + { + SelectedCamera = e.SelectedCamera + }); + } + + /// + /// Registers the actions for this messenger. + /// + /// + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, context) => SendFullStatus(id)); + + AddAction("/cameraListStatus", (id, content) => SendFullStatus(id)); + + AddAction("/selectCamera", (id, content) => + { + var cameraKey = content?.ToObject(); + + if (!string.IsNullOrEmpty(cameraKey)) + { + CameraController.SelectCamera(cameraKey); + } + else + { + throw new ArgumentException("Content must be a string representing the camera key"); + } + }); + } + + private void SendFullStatus(string clientId) + { + var state = new IHasCamerasStateMessage + { + CameraList = CameraController.Cameras, + SelectedCamera = CameraController.SelectedCamera + }; + + PostStatusMessage(state, clientId); + } + + + } + + /// + /// State message for devices that implement the IHasCameras interface. + /// + public class IHasCamerasStateMessage : DeviceStateMessageBase + { + /// + /// List of cameras available in the device. + /// + [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] + public List CameraList { get; set; } + + /// + /// The currently selected camera on the device. + /// + [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] + public CameraBase SelectedCamera { get; set; } + + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlMessenger.cs similarity index 71% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlMessenger.cs index cb83a26e..cce07dcd 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCamerasWithControlMessenger.cs @@ -12,7 +12,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Messenger for devices that implement the IHasCameras interface. /// - public class IHasCamerasWithControlsMessenger : MessengerBase + public class IHasCamerasWithControlMessenger : MessengerBase { /// /// Device being bridged that implements IHasCameras interface. @@ -26,7 +26,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// /// - public IHasCamerasWithControlsMessenger(string key, string messagePath, IHasCamerasWithControls cameraController) + public IHasCamerasWithControlMessenger(string key, string messagePath, IHasCamerasWithControls cameraController) : base(key, messagePath, cameraController) { CameraController = cameraController ?? throw new ArgumentNullException("cameraController"); @@ -81,8 +81,7 @@ namespace PepperDash.Essentials.AppServer.Messengers foreach (var cam in CameraController.Cameras) { - cameraList.Add(new KeyName - { + cameraList.Add(new KeyName{ Key = cam.Key, Name = cam.Name }); @@ -97,27 +96,10 @@ namespace PepperDash.Essentials.AppServer.Messengers }; } - string mode = ""; - - if (CameraController is IHasCameraAutoMode speakerTrackCodec) - { - mode = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue - ? eCameraControlMode.Auto.ToString().ToLower() - : eCameraControlMode.Manual.ToString().ToLower(); - } - - if (CameraController is IHasCameraOff cameraOffCodec) - { - if (cameraOffCodec.CameraIsOffFeedback.BoolValue) - mode = eCameraControlMode.Off.ToString().ToLower(); - } - var state = new IHasCamerasWithControlsStateMessage { CameraList = cameraList, - SelectedCamera = selectedCamera, - CameraMode = mode, - HasCameraAutoMode = CameraController is IHasCameraAutoMode, + SelectedCamera = selectedCamera }; PostStatusMessage(state, clientId); @@ -140,18 +122,6 @@ namespace PepperDash.Essentials.AppServer.Messengers /// [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] public IKeyName SelectedCamera { get; set; } - - /// - /// Indicates whether the device has any cameras. Null if unknown or not applicable. - /// - [JsonProperty("hasCameras", NullValueHandling = NullValueHandling.Ignore)] - public bool HasCameras { get; set; } = true; // Since this messenger should only be used for devices with cameras, default to true unless specified otherwise. - - [JsonProperty("hasCameraAutoMode", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasCameraAutoMode { get; set; } - - [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] - public string CameraMode { get; set; } } class KeyName : IKeyName diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs deleted file mode 100644 index b0f06e0c..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecCamerasMessenger.cs +++ /dev/null @@ -1,458 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Cameras; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing , including - /// sub-interface support for , - /// , and . - /// - public class IHasCodecCamerasMessenger : MessengerBase - { - private readonly VideoCodecBase _codec; - private readonly IHasCodecCameras _cameraCodec; - - /// - /// Initializes a new instance of the class. - /// - public IHasCodecCamerasMessenger(string key, string messagePath, VideoCodecBase codec) - : base(key, messagePath, codec) - { - _codec = codec ?? throw new ArgumentNullException(nameof(codec)); - _cameraCodec = codec as IHasCodecCameras ?? throw new ArgumentException("codec must implement IHasCodecCameras", nameof(codec)); - - _cameraCodec.CameraSelected += CameraCodec_CameraSelected; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - this.LogVerbose("Adding IHasCodecCameras Actions"); - - _cameraCodec.CameraSelected += CameraCodec_CameraSelected; - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/codecCamerasStatus", (id, content) => SendFullStatus(id)); - - AddAction("/cameraSelect", (id, content) => - { - var msg = content.ToObject>(); - _cameraCodec.SelectCamera(msg.Value); - }); - - MapCameraActions(); - - if (_codec is IHasCodecRoomPresets presetsCodec) - { - this.LogVerbose("Adding IHasCodecRoomPresets Actions"); - - presetsCodec.CodecRoomPresetsListHasChanged += PresetsCodec_CameraPresetsListHasChanged; - - AddAction("/cameraPreset", (id, content) => - { - var msg = content.ToObject>(); - presetsCodec.CodecRoomPresetSelect(msg.Value); - }); - - AddAction("/cameraPresetStore", (id, content) => - { - var msg = content.ToObject(); - presetsCodec.CodecRoomPresetStore(msg.ID, msg.Description); - }); - } - - if (_codec is IHasCameraAutoMode speakerTrackCodec) - { - this.LogVerbose("Adding IHasCameraAutoMode Actions"); - - speakerTrackCodec.CameraAutoModeIsOnFeedback.OutputChange += CameraAutoModeIsOnFeedback_OutputChange; - - AddAction("/cameraModeAuto", (id, content) => speakerTrackCodec.CameraAutoModeOn()); - AddAction("/cameraModeManual", (id, content) => speakerTrackCodec.CameraAutoModeOff()); - } - - if (_codec is IHasCameraOff cameraOffCodec) - { - this.LogVerbose("Adding IHasCameraOff Actions"); - - cameraOffCodec.CameraIsOffFeedback.OutputChange += CameraIsOffFeedback_OutputChange; - - AddAction("/cameraModeOff", (id, content) => cameraOffCodec.CameraOff()); - } - } - - private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs e) - { - try - { - MapCameraActions(); - PostSelectedCamera(); - } - catch (Exception ex) - { - this.LogError(ex, "Exception handling camera selected event"); - } - } - - private void MapCameraActions() - { - if (_cameraCodec.SelectedCamera == null) return; - - RemoveAction("/cameraUp"); - RemoveAction("/cameraDown"); - RemoveAction("/cameraLeft"); - RemoveAction("/cameraRight"); - RemoveAction("/cameraZoomIn"); - RemoveAction("/cameraZoomOut"); - RemoveAction("/cameraHome"); - - if (_cameraCodec.SelectedCamera is IHasCameraPtzControl camera) - { - AddAction("/cameraUp", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.TiltUp(); else camera.TiltStop(); - })); - - AddAction("/cameraDown", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.TiltDown(); else camera.TiltStop(); - })); - - AddAction("/cameraLeft", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.PanLeft(); else camera.PanStop(); - })); - - AddAction("/cameraRight", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.PanRight(); else camera.PanStop(); - })); - - AddAction("/cameraZoomIn", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.ZoomIn(); else camera.ZoomStop(); - })); - - AddAction("/cameraZoomOut", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) camera.ZoomOut(); else camera.ZoomStop(); - })); - - AddAction("/cameraHome", (id, content) => camera.PositionHome()); - - RemoveAction("/cameraAutoFocus"); - RemoveAction("/cameraFocusNear"); - RemoveAction("/cameraFocusFar"); - - if (_cameraCodec.SelectedCamera is IHasCameraFocusControl focusCamera) - { - AddAction("/cameraAutoFocus", (id, content) => focusCamera.TriggerAutoFocus()); - - AddAction("/cameraFocusNear", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) focusCamera.FocusNear(); else focusCamera.FocusStop(); - })); - - AddAction("/cameraFocusFar", (id, content) => HandleCameraPressAndHold(content, b => - { - if (b) focusCamera.FocusFar(); else focusCamera.FocusStop(); - })); - } - } - } - - private void HandleCameraPressAndHold(JToken content, Action cameraAction) - { - var state = content.ToObject>(); - var timerHandler = PressAndHoldHandler.GetPressAndHoldHandler(state.Value); - if (timerHandler == null) return; - timerHandler(state.Value, cameraAction); - cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase)); - } - - private void CameraAutoModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - PostCameraMode(); - } - - private void CameraIsOffFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - PostCameraMode(); - } - - private void PresetsCodec_CameraPresetsListHasChanged(object sender, EventArgs e) - { - PostCameraPresets(); - } - - private string GetCameraMode() - { - string m = ""; - - if (_codec is IHasCameraAutoMode speakerTrackCodec) - { - m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue - ? eCameraControlMode.Auto.ToString().ToLower() - : eCameraControlMode.Manual.ToString().ToLower(); - } - - if (_codec is IHasCameraOff cameraOffCodec && cameraOffCodec.CameraIsOffFeedback.BoolValue) - { - m = eCameraControlMode.Off.ToString().ToLower(); - } - - return m; - } - - private Camera GetSelectedCamera() - { - var camera = new Camera(); - - if (_cameraCodec.SelectedCameraFeedback != null) - camera.Key = _cameraCodec.SelectedCameraFeedback.StringValue; - - if (_cameraCodec.SelectedCamera != null) - { - camera.Name = _cameraCodec.SelectedCamera.Name; - - if (_cameraCodec.SelectedCamera is IHasCameraPtzControl ptz) - { - camera.Capabilities = new CameraCapabilities - { - CanPan = ptz is IHasCameraPanControl, - CanTilt = ptz is IHasCameraTiltControl, - CanZoom = ptz is IHasCameraZoomControl, - CanFocus = ptz is IHasCameraFocusControl, - }; - } - } - - if (_cameraCodec.ControllingFarEndCameraFeedback != null) - camera.IsFarEnd = _cameraCodec.ControllingFarEndCameraFeedback.BoolValue; - - return camera; - } - - private List GetCurrentPresets() - { - if (!(_codec is IHasCodecRoomPresets presetsCodec)) return null; - - if (_codec is IHasFarEndCameraControl farEnd && farEnd.ControllingFarEndCameraFeedback.BoolValue) - return presetsCodec.FarEndRoomPresets; - - return presetsCodec.NearEndPresets; - } - - private Camera GetSelectedCamera(IHasCodecCameras camerasCodec) - { - var camera = new Camera(); - - if (camerasCodec.SelectedCameraFeedback != null) - camera.Key = camerasCodec.SelectedCameraFeedback.StringValue; - if (camerasCodec.SelectedCamera != null) - { - camera.Name = camerasCodec.SelectedCamera.Name; - - if(camerasCodec.SelectedCamera is IHasCameraPtzControl cameraControls) - { - camera.Capabilities = new CameraCapabilities() - { - CanPan = cameraControls is IHasCameraPanControl, - CanTilt = cameraControls is IHasCameraTiltControl, - CanZoom = cameraControls is IHasCameraZoomControl, - CanFocus = cameraControls is IHasCameraFocusControl, - }; - }; - } - - if (camerasCodec.ControllingFarEndCameraFeedback != null) - camera.IsFarEnd = camerasCodec.ControllingFarEndCameraFeedback.BoolValue; - - - return camera; - } - - private void PostCameraMode() - { - try - { - PostStatusMessage(new IHasCodecCamerasStateMessage - { - CameraMode = GetCameraMode() - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting camera mode"); - } - } - - private void PostSelectedCamera() - { - try - { - PostStatusMessage(new IHasCodecCamerasStateMessage - { - Cameras = new CameraStatus { SelectedCamera = GetSelectedCamera() }, - Presets = GetCurrentPresets() - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting selected camera"); - } - } - - private void PostCameraPresets() - { - try - { - PostStatusMessage(new IHasCodecCamerasStateMessage - { - Presets = GetCurrentPresets() - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting camera presets"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - PostStatusMessage(new IHasCodecCamerasStateMessage - { - CameraMode = GetCameraMode(), - Cameras = new CameraStatus - { - CameraManualIsSupported = true, - CameraAutoIsSupported = _codec.SupportsCameraAutoMode, - CameraOffIsSupported = _codec.SupportsCameraOff, - CameraMode = GetCameraMode(), - Cameras = _cameraCodec.Cameras, - SelectedCamera = GetSelectedCamera(_cameraCodec) - }, - Presets = GetCurrentPresets() - }, id); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending full camera status"); - } - } - } - - public class IHasCodecCamerasStateMessage : DeviceStateMessageBase - { - [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] - public string CameraMode { get; set; } - - [JsonProperty("cameras", NullValueHandling = NullValueHandling.Ignore)] - public CameraStatus Cameras { get; set; } - - [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] - public List Presets { get; set; } - - [JsonProperty("cameraSupportsAutoMode", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraSupportsAutoMode { get; set; } - - [JsonProperty("cameraSupportsOffMode", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraSupportsOffMode { get; set; } - } - - /// - /// Represents a CameraStatus - /// - public class CameraStatus - { - [JsonProperty("cameraManualSupported", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraManualIsSupported { get; set; } - - [JsonProperty("cameraAutoSupported", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraAutoIsSupported { get; set; } - - [JsonProperty("cameraOffSupported", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraOffIsSupported { get; set; } - - - /// - /// Gets or sets the CameraMode - /// - [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] - public string CameraMode { get; set; } - - - /// - /// Gets or sets the Cameras - /// - [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] - public List Cameras { get; set; } - - - /// - /// Gets or sets the SelectedCamera - /// - [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] - public Camera SelectedCamera { get; set; } - } - - /// - /// Represents a Camera - /// - public class Camera - { - - /// - /// Gets or sets the Key - /// - [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] - public string Key { get; set; } - - - /// - /// Gets or sets the Name - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } - - [JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsFarEnd { get; set; } - - - /// - /// Gets or sets the Capabilities - /// - [JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)] - public CameraCapabilities Capabilities { get; set; } - } - - /// - /// Represents a CameraCapabilities - /// - public class CameraCapabilities - { - [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] - public bool? CanPan { get; set; } - - [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] - public bool? CanTilt { get; set; } - - [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] - public bool? CanZoom { get; set; } - - [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] - public bool? CanFocus { get; set; } - - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecLayoutsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecLayoutsMessenger.cs deleted file mode 100644 index 9a71c02a..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecLayoutsMessenger.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using Newtonsoft.Json; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IHasCodecLayoutsMessenger : MessengerBase - { - private readonly IHasCodecLayouts _layouts; - - /// - /// Initializes a new instance of the class. - /// - public IHasCodecLayoutsMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _layouts = device as IHasCodecLayouts ?? throw new ArgumentException("device must implement IHasCodecLayouts", nameof(device)); - - _layouts.LocalLayoutFeedback.OutputChange += LocalLayoutFeedback_OutputChange; - } - - private void LocalLayoutFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - SendFullStatus(); - } - - private void SendFullStatus(string id = null) - { - PostStatusMessage(new IHasCodecLayoutsStateMessage - { - CurrentLayout = _layouts.LocalLayoutFeedback.StringValue - }, id); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/layoutStatus", (id, content) => SendFullStatus(id)); - - AddAction("/cameraRemoteView", (id, content) => _layouts.LocalLayoutToggle()); - AddAction("/cameraLayout", (id, content) => _layouts.LocalLayoutToggle()); - } - } - - /// - /// State message for - /// - public class IHasCodecLayoutsStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the current layout of the codec - /// - [JsonProperty("currentLayout", NullValueHandling = NullValueHandling.Ignore)] - public string CurrentLayout { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs deleted file mode 100644 index 608b24b7..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecRoomPresetsMessenger.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Cameras; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasCodecRoomPresetsMessenger : MessengerBase - { - private readonly IHasCodecRoomPresets _presets; - private readonly EssentialsDevice _device; - - public IHasCodecRoomPresetsMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _device = device ?? throw new ArgumentNullException(nameof(device)); - _presets = device as IHasCodecRoomPresets ?? throw new ArgumentNullException(nameof(device)); - _presets.CodecRoomPresetsListHasChanged += Presets_ListHasChanged; - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/roomPresetsStatus", (id, content) => SendFullStatus(id)); - } - - private void Presets_ListHasChanged(object sender, EventArgs e) - { - try - { - PostStatusMessage(new IHasCodecRoomPresetsStateMessage - { - Presets = GetCurrentPresets() - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting codec room presets"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasCodecRoomPresetsStateMessage - { - Presets = GetCurrentPresets() - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending room presets full status"); - } - } - - private List GetCurrentPresets() - { - if (_device is IHasFarEndCameraControl farEndControl && - farEndControl.ControllingFarEndCameraFeedback.BoolValue) - return _presets.FarEndRoomPresets; - - return _presets.NearEndPresets; - } - } - - public class IHasCodecRoomPresetsStateMessage : DeviceStateMessageBase - { - [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] - public List Presets { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs deleted file mode 100644 index 0b534dc5..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCodecSelfViewMessenger.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IHasCodecSelfViewMessenger : MessengerBase - { - private readonly IHasCodecSelfView _selfView; - - /// - /// Initializes a new instance of the class. - /// - public IHasCodecSelfViewMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _selfView = device as IHasCodecSelfView ?? throw new ArgumentException("device must implement IHasCodecSelfView", nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/cameraSelfViewStatus", (id, content) => SendFullStatus(id)); - - AddAction("/cameraSelfView", (id, content) => _selfView.SelfViewModeToggle()); - - _selfView.SelfviewIsOnFeedback.OutputChange += SelfviewIsOnFeedback_OutputChange; - } - - private void SelfviewIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - PostCameraSelfView(); - } - - private void SendFullStatus(string id = null) - { - PostCameraSelfView(id); - } - - private void PostCameraSelfView(string id = null) - { - try - { - PostStatusMessage(new IHasCodecSelfViewStateMessage - { - CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue, - ShowSelfViewByDefault = _selfView.ShowSelfViewByDefault - }, id); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting camera self view"); - } - } - } - - /// - /// State message for - /// - public class IHasCodecSelfViewStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets a value indicating whether the codec's self view is currently on. Null if unknown or not applicable. - /// - /// - [JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)] - public bool? CameraSelfViewIsOn { get; set; } - - /// - /// Gets or sets a value indicating whether the codec is set to show self view by default. Null if unknown or not applicable. - /// - /// - [JsonProperty("showSelfViewByDefault", NullValueHandling = NullValueHandling.Ignore)] - public bool? ShowSelfViewByDefault { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs deleted file mode 100644 index 69ebadb0..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasContentSharingMessenger.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasContentSharingMessenger : MessengerBase - { - private readonly IHasContentSharing _sharing; - - public IHasContentSharingMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _sharing = device as IHasContentSharing ?? throw new ArgumentNullException(nameof(device)); - _sharing.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange; - _sharing.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange; - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/contentSharingStatus", (id, content) => SendFullStatus(id)); - AddAction("/sharingStart", (id, content) => _sharing.StartSharing()); - AddAction("/sharingStop", (id, content) => _sharing.StopSharing()); - } - - private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - try - { - PostStatusMessage(new IHasContentSharingStateMessage - { - SharingContentIsOn = e.BoolValue - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting sharing content is on"); - } - } - - private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - try - { - PostStatusMessage(new IHasContentSharingStateMessage - { - SharingSource = e.StringValue - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting sharing source"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasContentSharingStateMessage - { - SharingContentIsOn = _sharing.SharingContentIsOnFeedback.BoolValue, - SharingSource = _sharing.SharingSourceFeedback.StringValue - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending content sharing full status"); - } - } - } - - public class IHasContentSharingStateMessage : DeviceStateMessageBase - { - [JsonProperty("sharingContentIsOn", NullValueHandling = NullValueHandling.Ignore)] - public bool? SharingContentIsOn { get; set; } - - [JsonProperty("sharingSource", NullValueHandling = NullValueHandling.Ignore)] - public string SharingSource { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCurrentSourceInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCurrentSourceInfoMessenger.cs new file mode 100644 index 00000000..6c0f1a0a --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasCurrentSourceInfoMessenger.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Represents a IHasCurrentSourceInfoMessenger + /// + public class IHasCurrentSourceInfoMessenger : MessengerBase + { + private readonly IHasCurrentSourceInfoChange sourceDevice; + + public IHasCurrentSourceInfoMessenger(string key, string messagePath, IHasCurrentSourceInfoChange device) : base(key, messagePath, device as IKeyName) + { + sourceDevice = device; + } + + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + + AddAction("/currentSourceInfoStatus", (id, content) => SendFullStatus(id)); + + sourceDevice.CurrentSourceChange += (sender, e) => + { + switch (e) + { + case ChangeType.DidChange: + { + PostStatusMessage(JToken.FromObject(new + { + currentSourceKey = string.IsNullOrEmpty(sourceDevice.CurrentSourceInfoKey) ? string.Empty : sourceDevice.CurrentSourceInfoKey, + currentSource = sourceDevice.CurrentSourceInfo + })); + break; + } + } + }; + } + + private void SendFullStatus(string id = null) + { + var message = new CurrentSourceStateMessage + { + CurrentSourceKey = sourceDevice.CurrentSourceInfoKey, + CurrentSource = sourceDevice.CurrentSourceInfo + }; + + PostStatusMessage(message, id); + } + } + + /// + /// Represents a CurrentSourceStateMessage + /// + public class CurrentSourceStateMessage : DeviceStateMessageBase + { + + /// + /// Gets or sets the CurrentSourceKey + /// + [JsonProperty("currentSourceKey", NullValueHandling = NullValueHandling.Ignore)] + public string CurrentSourceKey { get; set; } + + + /// + /// Gets or sets the CurrentSource + /// + [JsonProperty("currentSource")] + public SourceListItem CurrentSource { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs deleted file mode 100644 index e4b917b0..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDialerMessenger.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasDialerMessenger : MessengerBase - { - private readonly IHasDialer _dialer; - - /// - public IHasDialerMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _dialer = device as IHasDialer ?? throw new ArgumentNullException(nameof(device)); - _dialer.CallStatusChange += Dialer_CallStatusChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/dialStatus", (id, content) => SendFullStatus(id)); - - AddAction("/dial", (id, content) => - { - var value = content.ToObject>(); - _dialer.Dial(value.Value); - }); - - - AddAction("/endAllCalls", (id, content) => _dialer.EndAllCalls()); - - AddAction("/dtmf", (id, content) => - { - var s = content.ToObject>(); - _dialer.SendDtmf(s.Value); - }); - - AddAction("/acceptCall", (id, content) => - { - var callItem = content.ToObject(); - _dialer.AcceptCall(callItem); - }); - - AddAction("/rejectCall", (id, content) => - { - var callItem = content.ToObject(); - _dialer.RejectCall(callItem); - }); - } - - private void Dialer_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) - { - try - { - var state = new IHasDialerStateMessage - { - IsInCall = _dialer.IsInCall, - CallItem = e.CallItem - }; - - PostStatusMessage(state); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting dialer call status"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasDialerStateMessage - { - IsInCall = _dialer.IsInCall, - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending dialer full status"); - } - } - } - - /// - /// Message class representing the state of a device implementing - /// - public class IHasDialerStateMessage : DeviceStateMessageBase - { - /// - /// Indicates whether the device is currently in a call - /// - [JsonProperty("isInCall", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsInCall { get; set; } - - [JsonProperty("callItem", NullValueHandling = NullValueHandling.Ignore)] - public CodecActiveCallItem CallItem { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs deleted file mode 100644 index 3c87a5bc..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasDirectoryMessenger.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IHasDirectoryMessenger : MessengerBase - { - private readonly IHasDirectory _directory; - - /// - /// Initializes a new instance of the class. - /// - public IHasDirectoryMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _directory = device as IHasDirectory ?? throw new ArgumentException("device must implement IHasDirectory", nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/directoryStatus", (id, content) => SendFullStatus(id)); - - AddAction("/getDirectory", (id, content) => GetDirectoryRoot()); - - AddAction("/setCurrentDirectoryToRoot", (id, content) => _directory.SetCurrentDirectoryToRoot()); - - AddAction("/directoryById", (id, content) => - { - var msg = content.ToObject>(); - GetDirectory(msg.Value); - }); - - AddAction("/getDirectoryPareentFolderContents", (id, content) => _directory.GetDirectoryParentFolderContents()); - - AddAction("/directorySearch", (id, content) => - { - var msg = content.ToObject>(); - _directory.SearchDirectory(msg.Value); - }); - - AddAction("/directoryBack", (id, content) => GetPreviousDirectory()); - - _directory.DirectoryResultReturned += DirectoryResultReturned; - _directory.PhonebookSyncState.InitialSyncCompleted += PhonebookSyncState_InitialSyncCompleted; - - _directory.CurrentDirectoryResultIsNotDirectoryRoot.OutputChange += (s, e) => - { - PostStatusMessage(new IHasDirectoryStateMessage - { - DirectorySelectedFolderIsNotRoot = _directory.CurrentDirectoryResultIsNotDirectoryRoot?.BoolValue - }); - }; - } - - private void DirectoryResultReturned(object sender, DirectoryEventArgs e) - { - SendDirectory(e.Directory); - } - - private void SendDirectory(CodecDirectory directory) - { - try - { - this.LogVerbose("Sending Directory. Directory Item Count: {directoryItemCount}", directory.CurrentDirectoryResults.Count); - Task.Run(() => PostStatusMessage(new IHasDirectoryStateMessage - { - // DirectoryRoot is otherwise only sent once, in response to an explicit /fullStatus - // request. Since the root directory can keep growing after that (e.g. contacts - // trickling in from a paged/async phonebook sync), include it here too so it stays - // current as new directory results arrive, not just on first subscribe. - DirectoryRoot = _directory.DirectoryRoot, - CurrentDirectory = directory - })); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending directory"); - } - } - - private void PhonebookSyncState_InitialSyncCompleted(object sender, EventArgs e) - { - try - { - PostStatusMessage(new IHasDirectoryStateMessage - { - DirectoryRoot = _directory.DirectoryRoot, - InitialPhonebookSyncComplete = true - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting phonebook sync state"); - } - } - - private void GetDirectory(string id) - { - _directory.GetDirectoryFolderContents(id); - } - - private void GetDirectoryRoot() - { - try - { - if (!_directory.PhonebookSyncState.InitialSyncComplete) - { - PostStatusMessage(new IHasDirectoryStateMessage - { - InitialPhonebookSyncComplete = false - }); - return; - } - - _directory.SetCurrentDirectoryToRoot(); - } - catch (Exception ex) - { - this.LogError(ex, "Error getting directory root"); - } - } - - private void GetPreviousDirectory() - { - _directory.GetDirectoryParentFolderContents(); - } - - private void SendFullStatus(string id = null) - { - PostStatusMessage(new IHasDirectoryStateMessage - { - DirectoryRoot = _directory.DirectoryRoot, - CurrentDirectory = _directory.CurrentDirectoryResult, - InitialPhonebookSyncComplete = _directory.PhonebookSyncState.InitialSyncComplete, - HasDirectory = true, - HasDirectorySearch = true, - DirectorySelectedFolderName = _directory.CurrentDirectoryResult?.CurrentDirectoryResults?.Count > 0 ? _directory.CurrentDirectoryResult.CurrentDirectoryResults[0].Name : null, - DirectorySelectedFolderIsNotRoot = _directory.CurrentDirectoryResultIsNotDirectoryRoot?.BoolValue - }, id); - } - } - - public class IHasDirectoryStateMessage : DeviceStateMessageBase - { - [JsonProperty("directoryRoot", NullValueHandling = NullValueHandling.Ignore)] - public CodecDirectory DirectoryRoot { get; set; } - - [JsonProperty("currentDirectory", NullValueHandling = NullValueHandling.Ignore)] - public CodecDirectory CurrentDirectory { get; set; } - - [JsonProperty("initialPhonebookSyncComplete", NullValueHandling = NullValueHandling.Ignore)] - public bool? InitialPhonebookSyncComplete { get; set; } - - [JsonProperty("hasDirectory", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasDirectory { get; set; } - - [JsonProperty("hasDirectorySearch", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasDirectorySearch { get; set; } - - /// - /// Gets or sets the DirectorySelectedFolderName - /// - [JsonProperty("directorySelectedFolderName", NullValueHandling = NullValueHandling.Ignore)] - public string DirectorySelectedFolderName { get; set; } - - [JsonProperty("directorySelectedFolderIsNotRoot", NullValueHandling = NullValueHandling.Ignore)] - public bool? DirectorySelectedFolderIsNotRoot { get; set; } - - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs deleted file mode 100644 index c736bf82..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasFarEndContentStatusMessenger.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IHasFarEndContentStatusMessenger : MessengerBase - { - private readonly IHasFarEndContentStatus device; - - /// - /// Initializes a new instance of the class. - /// - public IHasFarEndContentStatusMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - this.device = device as IHasFarEndContentStatus ?? throw new ArgumentException("device must implement IHasFarEndContentStatus", nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/farEndContentStatus", (id, content) => SendFullStatus(id)); - - device.ReceivingContent.OutputChange += (sender, args) => PostReceivingContent(args.BoolValue); - } - - private void PostReceivingContent(bool receivingContent) - { - try - { - PostStatusMessage(new IHasFarEndContentStatusStateMessage - { - ReceivingContent = receivingContent - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting receiving content"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasFarEndContentStatusStateMessage - { - ReceivingContent = device.ReceivingContent.BoolValue - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting full status"); - } - } - } - - /// - /// Message class representing the state of a device implementing - /// - public class IHasFarEndContentStatusStateMessage : DeviceStateMessageBase - { - /// - /// Indicates whether the device is currently receiving content from the far end - /// - [JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)] - public bool? ReceivingContent { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasInputsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasInputsMessenger.cs index 8d8949ed..57ab2617 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasInputsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasInputsMessenger.cs @@ -26,8 +26,6 @@ namespace PepperDash.Essentials.AppServer.Messengers itemDevice = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -92,10 +90,10 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class IHasInputsStateMessage : DeviceStateMessageBase { + [JsonProperty("inputs")] /// /// Gets or sets the Inputs /// - [JsonProperty("inputs")] public Inputs Inputs { get; set; } } @@ -104,18 +102,14 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class Inputs { - /// - /// Gets or sets the Items - /// The key is the input key, and the value is the input name - /// [JsonProperty("items")] public Dictionary Items { get; set; } + [JsonProperty("currentItem")] /// /// Gets or sets the CurrentItem /// - [JsonProperty("currentItem")] public TKey CurrentItem { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs deleted file mode 100644 index 7aeb6765..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasMeetingInfoMessenger.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasMeetingInfoMessenger : MessengerBase - { - private readonly IHasMeetingInfo _meetingInfo; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public IHasMeetingInfoMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _meetingInfo = device as IHasMeetingInfo ?? throw new ArgumentNullException(nameof(device)); - _meetingInfo.MeetingInfoChanged += MeetingInfo_Changed; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/meetingInfoStatus", (id, content) => SendFullStatus(id)); - } - - private void MeetingInfo_Changed(object sender, MeetingInfoEventArgs e) - { - try - { - PostStatusMessage(new IHasMeetingInfoStateMessage - { - MeetingInfo = _meetingInfo.MeetingInfo - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting meeting info"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasMeetingInfoStateMessage - { - MeetingInfo = _meetingInfo.MeetingInfo - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending meeting info full status"); - } - } - } - - /// - /// Message class for devices implementing - /// - public class IHasMeetingInfoStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the MeetingInfo - /// - [JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)] - public MeetingInfo MeetingInfo { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs deleted file mode 100644 index 855a4aa7..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasNamedRoutingSlotsMessenger.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing — sends named - /// input/output slots with per-signal-type current-route feedback, which the bare - /// (registered for every - /// device, including this one) cannot provide. - /// - public class IHasNamedRoutingSlotsMessenger : MessengerBase - { - private readonly IHasNamedRoutingSlots _device; - - public IHasNamedRoutingSlotsMessenger(string key, string messagePath, IHasNamedRoutingSlots device) - : base(key, messagePath, device as IKeyName) - { - _device = device; - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - foreach (var output in _device.OutputSlots.Values) - { - output.OutputSlotChanged += (sender, args) => SendFullStatus(); - } - } - - private static RoutingSlotMessage BuildSlotMessage(IRoutingSlotInfo slot) => new RoutingSlotMessage - { - Key = slot.Key, - Name = slot.Name, - SlotNumber = slot.SlotNumber, - SupportedSignalTypes = slot.SupportedSignalTypes.ToString() - }; - - private void SendFullStatus(string id = null) - { - var inputSlots = _device.InputSlots.ToDictionary(kvp => kvp.Key, kvp => BuildSlotMessage(kvp.Value)); - - var outputSlots = _device.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => - { - var message = BuildSlotMessage(kvp.Value); - message.CurrentRouteInputKeys = kvp.Value.CurrentRouteInputKeys - .ToDictionary(r => r.Key.ToString(), r => r.Value); - return message; - }); - - var content = JToken.FromObject(new - { - inputSlots, - outputSlots - }); - - PostStatusMessage(content, MessagePath, id); - } - } - - public class RoutingSlotMessage - { - [JsonProperty("key")] - public string Key { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("slotNumber")] - public int SlotNumber { get; set; } - - [JsonProperty("supportedSignalTypes")] - public string SupportedSignalTypes { get; set; } - - [JsonProperty("currentRouteInputKeys", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary CurrentRouteInputKeys { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantAudioMuteMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantAudioMuteMessenger.cs deleted file mode 100644 index e7f6cebe..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantAudioMuteMessenger.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using PepperDash.Essentials.AppServer; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Mobile Control messenger for : - /// mute-all and per-participant audio/video mute toggles. Action-only (no status of its own). - /// - public class IHasParticipantAudioMuteMessenger : MessengerBase - { - private readonly IHasParticipantAudioMute _codec; - - public IHasParticipantAudioMuteMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _codec = device as IHasParticipantAudioMute ?? throw new ArgumentNullException(nameof(device)); - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/muteAllParticipants", (id, content) => _codec.MuteAudioForAllParticipants()); - AddAction("/toggleParticipantAudioMute", (id, content) => - { - var i = content?.ToObject>(); - if (i != null) _codec.ToggleAudioForParticipant(i.Value); - }); - AddAction("/toggleParticipantVideoMute", (id, content) => - { - var i = content?.ToObject>(); - if (i != null) _codec.ToggleVideoForParticipant(i.Value); - }); - } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantVideoMuteMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantVideoMuteMessenger.cs deleted file mode 100644 index 449a51d0..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasParticipantVideoMuteMessenger.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; -using PepperDash.Essentials.AppServer; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Mobile Control messenger for : - /// per-participant video mute/unmute/toggle. Action-only (no status of its own). - /// - public class IHasParticipantVideoMuteMessenger : MessengerBase - { - private readonly IHasParticipantVideoMute _codec; - - public IHasParticipantVideoMuteMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _codec = device as IHasParticipantVideoMute ?? throw new ArgumentNullException(nameof(device)); - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/muteVideoForParticipant", (id, content) => - { - var i = content?.ToObject>(); - if (i != null) _codec.MuteVideoForParticipant(i.Value); - }); - AddAction("/unmuteVideoForParticipant", (id, content) => - { - var i = content?.ToObject>(); - if (i != null) _codec.UnmuteVideoForParticipant(i.Value); - }); - AddAction("/toggleParticipantVideoMute", (id, content) => - { - var i = content?.ToObject>(); - if (i != null) _codec.ToggleVideoForParticipant(i.Value); - }); - } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasPowerControlWithFeedbackMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasPowerControlWithFeedbackMessenger.cs index b16611cf..525a6d6d 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasPowerControlWithFeedbackMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasPowerControlWithFeedbackMessenger.cs @@ -70,4 +70,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("powerState", NullValueHandling = NullValueHandling.Ignore)] public bool? PowerState { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs deleted file mode 100644 index 3b704087..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasReadyMessenger.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasReadyMessenger : MessengerBase - { - private readonly IHasReady _hasReady; - - public IHasReadyMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _hasReady = device as IHasReady ?? throw new ArgumentNullException(nameof(device)); - _hasReady.IsReadyEvent += HasReady_IsReadyEvent; - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/isReady", (id, content) => SendFullStatus(id)); - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - } - - private void HasReady_IsReadyEvent(object sender, IsReadyEventArgs e) - { - try - { - PostStatusMessage(new IHasReadyStateMessage - { - IsReady = e.IsReady - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting ready state"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasReadyStateMessage - { - IsReady = _hasReady.IsReady - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending ready full status"); - } - } - } - - public class IHasReadyStateMessage : DeviceStateMessageBase - { - [JsonProperty("isReady", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsReady { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasScheduleAwarenessMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasScheduleAwarenessMessenger.cs index 563fa3bd..76598cee 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasScheduleAwarenessMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasScheduleAwarenessMessenger.cs @@ -5,76 +5,114 @@ using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Essentials.Devices.Common.Codec; -namespace PepperDash.Essentials.AppServer.Messengers; - -public class IHasScheduleAwarenessMessenger : MessengerBase +namespace PepperDash.Essentials.AppServer.Messengers { - public IHasScheduleAwareness ScheduleSource { get; private set; } - - public IHasScheduleAwarenessMessenger(string key, IHasScheduleAwareness scheduleSource, string messagePath) - : base(key, messagePath, scheduleSource as IKeyName) + /// + /// Represents a IHasScheduleAwarenessMessenger + /// + public class IHasScheduleAwarenessMessenger : MessengerBase { - ScheduleSource = scheduleSource ?? throw new ArgumentNullException("scheduleSource"); - ScheduleSource.CodecSchedule.MeetingsListHasChanged += new EventHandler(CodecSchedule_MeetingsListHasChanged); - ScheduleSource.CodecSchedule.MeetingEventChange += new EventHandler(CodecSchedule_MeetingEventChange); - } + /// + /// Gets or sets the ScheduleSource + /// + public IHasScheduleAwareness ScheduleSource { get; private set; } - protected override void RegisterActions() - { - AddAction("/schedule/fullStatus", (id, content) => SendFullScheduleObject(id)); - } - - private void CodecSchedule_MeetingEventChange(object sender, MeetingEventArgs e) - { - PostStatusMessage(JToken.FromObject(new MeetingChangeMessage + public IHasScheduleAwarenessMessenger(string key, IHasScheduleAwareness scheduleSource, string messagePath) + : base(key, messagePath, scheduleSource as IKeyName) { - MeetingChange = new MeetingChange - { - ChangeType = e.ChangeType.ToString(), - Meeting = e.Meeting - } - }) - ); - } + ScheduleSource = scheduleSource ?? throw new ArgumentNullException("scheduleSource"); + ScheduleSource.CodecSchedule.MeetingsListHasChanged += new EventHandler(CodecSchedule_MeetingsListHasChanged); + ScheduleSource.CodecSchedule.MeetingEventChange += new EventHandler(CodecSchedule_MeetingEventChange); + } - private void CodecSchedule_MeetingsListHasChanged(object sender, EventArgs e) - { - SendFullScheduleObject(); + protected override void RegisterActions() + { + AddAction("/schedule/fullStatus", (id, content) => SendFullScheduleObject(id)); + + AddAction("/schedule/status", (id, content) => SendFullScheduleObject(id)); + } + + private void CodecSchedule_MeetingEventChange(object sender, MeetingEventArgs e) + { + PostStatusMessage(JToken.FromObject(new MeetingChangeMessage + { + MeetingChange = new MeetingChange + { + ChangeType = e.ChangeType.ToString(), + Meeting = e.Meeting + } + }) + ); + } + + private void CodecSchedule_MeetingsListHasChanged(object sender, EventArgs e) + { + SendFullScheduleObject(); + } + + /// + /// Helper method to send the full schedule data + /// + private void SendFullScheduleObject(string id = null) + { + PostStatusMessage(new FullScheduleMessage + { + Meetings = ScheduleSource.CodecSchedule.Meetings, + MeetingWarningMinutes = ScheduleSource.CodecSchedule.MeetingWarningMinutes + }, id); + } } /// - /// Helper method to send the full schedule data + /// Represents a FullScheduleMessage /// - private void SendFullScheduleObject(string id = null) + public class FullScheduleMessage : DeviceStateMessageBase { - PostStatusMessage(new FullScheduleMessage - { - Meetings = ScheduleSource.CodecSchedule.Meetings, - MeetingWarningMinutes = ScheduleSource.CodecSchedule.MeetingWarningMinutes - }, id); + + /// + /// Gets or sets the Meetings + /// + [JsonProperty("meetings", NullValueHandling = NullValueHandling.Ignore)] + public List Meetings { get; set; } + + + /// + /// Gets or sets the MeetingWarningMinutes + /// + [JsonProperty("meetingWarningMinutes", NullValueHandling = NullValueHandling.Ignore)] + public int MeetingWarningMinutes { get; set; } } -} -public class FullScheduleMessage : DeviceStateMessageBase -{ - [JsonProperty("meetings", NullValueHandling = NullValueHandling.Ignore)] - public List Meetings { get; set; } + /// + /// Represents a MeetingChangeMessage + /// + public class MeetingChangeMessage + { - [JsonProperty("meetingWarningMinutes", NullValueHandling = NullValueHandling.Ignore)] - public int MeetingWarningMinutes { get; set; } -} + /// + /// Gets or sets the MeetingChange + /// + [JsonProperty("meetingChange", NullValueHandling = NullValueHandling.Ignore)] + public MeetingChange MeetingChange { get; set; } + } -public class MeetingChangeMessage -{ - [JsonProperty("meetingChange", NullValueHandling = NullValueHandling.Ignore)] - public MeetingChange MeetingChange { get; set; } -} + /// + /// Represents a MeetingChange + /// + public class MeetingChange + { -public class MeetingChange -{ - [JsonProperty("changeType", NullValueHandling = NullValueHandling.Ignore)] - public string ChangeType { get; set; } + /// + /// Gets or sets the ChangeType + /// + [JsonProperty("changeType", NullValueHandling = NullValueHandling.Ignore)] + public string ChangeType { get; set; } - [JsonProperty("meeting", NullValueHandling = NullValueHandling.Ignore)] - public Meeting Meeting { get; set; } + + /// + /// Gets or sets the Meeting + /// + [JsonProperty("meeting", NullValueHandling = NullValueHandling.Ignore)] + public Meeting Meeting { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs deleted file mode 100644 index da493d4d..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStandbyModeMessenger.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasStandbyModeMessenger : MessengerBase - { - private readonly IHasStandbyMode _standby; - - /// - /// Initializes a new instance of the class. - /// - public IHasStandbyModeMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _standby = device as IHasStandbyMode ?? throw new ArgumentNullException(nameof(device)); - _standby.StandbyIsOnFeedback.OutputChange += StandbyIsOnFeedback_OutputChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/standbyStatus", (id, content) => SendFullStatus(id)); - - AddAction("/standbyOn", (id, content) => _standby.StandbyActivate()); - AddAction("/standbyOff", (id, content) => _standby.StandbyDeactivate()); - } - - private void StandbyIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - try - { - PostStatusMessage(new IHasStandbyModeStateMessage - { - StandbyIsOn = e.BoolValue - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting standby state"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasStandbyModeStateMessage - { - StandbyIsOn = _standby.StandbyIsOnFeedback.BoolValue - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending standby full status"); - } - } - } - - /// - /// State message for - /// - public class IHasStandbyModeStateMessage : DeviceStateMessageBase - { - /// - /// Indicates whether the device is in standby mode. Null if unknown or not applicable. - /// - [JsonProperty("standbyIsOn", NullValueHandling = NullValueHandling.Ignore)] - public bool? StandbyIsOn { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs deleted file mode 100644 index bc8559c0..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHasStartMeetingMessenger.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IHasStartMeetingMessenger : MessengerBase - { - private readonly IHasStartMeeting _startMeeting; - - /// - /// Initializes a new instance of the class. - /// - /// The key for the messenger. - /// The message path for the messenger. - /// The device implementing . - /// Thrown if the device does not implement . - public IHasStartMeetingMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _startMeeting = device as IHasStartMeeting ?? throw new ArgumentNullException(nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/startMeetingStatus", (id, content) => SendFullStatus(id)); - - AddAction("/startMeeting", (id, content) => - { - var msg = content.ToObject>(); - _startMeeting.StartMeeting(msg?.Value ?? _startMeeting.DefaultMeetingDurationMin); - }); - - AddAction("/leaveMeeting", (id, content) => _startMeeting.LeaveMeeting()); - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IHasStartMeetingStateMessage - { - DefaultMeetingDurationMin = _startMeeting.DefaultMeetingDurationMin - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending start meeting full status"); - } - } - } - - /// - /// State message for devices implementing - /// - public class IHasStartMeetingStateMessage : DeviceStateMessageBase - { - /// - /// Indicates whether the device supports ad-hoc meetings (meetings started from the device rather than an external calendar invite) - /// - /// - [JsonProperty("supportsAdHocMeeting", NullValueHandling = NullValueHandling.Ignore)] - public bool SupportsAdHocMeeting { get; set; } = true; - - /// - /// The default meeting duration in minutes for meetings started from the device. Null if unknown or not applicable. - /// - [JsonProperty("defaultMeetingDurationMin", NullValueHandling = NullValueHandling.Ignore)] - public uint DefaultMeetingDurationMin { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHumiditySensor.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHumiditySensor.cs index 1fad0641..6bd04a74 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHumiditySensor.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IHumiditySensor.cs @@ -12,19 +12,12 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly IHumiditySensor device; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// public IHumiditySensorMessenger(string key, IHumiditySensor device, string messagePath) : base(key, messagePath, device as IKeyName) { this.device = device; } - /// protected override void RegisterActions() { base.RegisterActions(); @@ -59,4 +52,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("humidity")] public string Humidity { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILevelControlsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILevelControlsMessenger.cs index 6ddb671d..03bfc80f 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILevelControlsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILevelControlsMessenger.cs @@ -14,18 +14,11 @@ namespace PepperDash.Essentials.AppServer.Messengers { private ILevelControls levelControlsDevice; - /// - /// Create an instance of the class. - /// - /// - /// - /// public ILevelControlsMessenger(string key, string messagePath, ILevelControls device) : base(key, messagePath, device as IKeyName) { levelControlsDevice = device; } - /// protected override void RegisterActions() { base.RegisterActions(); @@ -94,9 +87,6 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class LevelControlStateMessage : DeviceStateMessageBase { - /// - /// Gets or sets the Levels - /// [JsonProperty("levelControls")] public Dictionary Levels { get; set; } } @@ -106,16 +96,13 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class LevelControlRequestMessage { + [JsonProperty("key")] /// /// Gets or sets the Key /// - [JsonProperty("key")] public string Key { get; set; } - /// - /// Represents a Volume - /// [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] public ushort? Level { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IMatrixRoutingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IMatrixRoutingMessenger.cs new file mode 100644 index 00000000..ec718919 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IMatrixRoutingMessenger.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Routing; +using Serilog.Events; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Messenger for devices that implment IMatrixRouting + /// + public class IMatrixRoutingMessenger : MessengerBase + { + private readonly IMatrixRouting matrixDevice; + public IMatrixRoutingMessenger(string key, string messagePath, IMatrixRouting device) : base(key, messagePath, device as IKeyName) + { + matrixDevice = device; + } + + protected override void RegisterActions() + { + base.RegisterActions(); + + AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + + AddAction("/matrixStatus", (id, content) => SendFullStatus(id)); + + AddAction("/route", (id, content) => + { + var request = content.ToObject(); + + matrixDevice.Route(request.InputKey, request.OutputKey, request.RouteType); + }); + + foreach (var output in matrixDevice.OutputSlots) + { + var key = output.Key; + var outputSlot = output.Value; + + outputSlot.OutputSlotChanged += (sender, args) => + { + PostStatusMessage(JToken.FromObject(new + { + outputs = matrixDevice.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingOutput(kvp.Value)) + })); + }; + } + + foreach (var input in matrixDevice.InputSlots) + { + var key = input.Key; + var inputSlot = input.Value; + + inputSlot.VideoSyncChanged += (sender, args) => + { + PostStatusMessage(JToken.FromObject(new + { + inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value)) + })); + }; + + inputSlot.IsOnline.OutputChange += (sender, args) => + { + PostStatusMessage(JToken.FromObject(new + { + inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value)) + })); + }; + } + } + + private void SendFullStatus(string id = null) + { + try + { + Debug.LogMessage(LogEventLevel.Verbose, "InputCount: {inputCount}, OutputCount: {outputCount}", this, matrixDevice.InputSlots.Count, matrixDevice.OutputSlots.Count); + var message = new MatrixStateMessage + { + Outputs = matrixDevice.OutputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingOutput(kvp.Value)), + Inputs = matrixDevice.InputSlots.ToDictionary(kvp => kvp.Key, kvp => new RoutingInput(kvp.Value)), + }; + + + PostStatusMessage(message, id); + } + catch (Exception e) + { + Debug.LogMessage(e, "Exception Getting full status: {@exception}", this, e); + } + } + } + + /// + /// Represents a MatrixStateMessage + /// + public class MatrixStateMessage : DeviceStateMessageBase + { + [JsonProperty("outputs")] + public Dictionary Outputs; + + [JsonProperty("inputs")] + public Dictionary Inputs; + } + + /// + /// Represents a RoutingInput + /// + public class RoutingInput + { + private IRoutingInputSlot _input; + + [JsonProperty("txDeviceKey", NullValueHandling = NullValueHandling.Ignore)] + public string TxDeviceKey => _input?.TxDeviceKey; + + [JsonProperty("slotNumber", NullValueHandling = NullValueHandling.Ignore)] + public int? SlotNumber => _input?.SlotNumber; + + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + [JsonProperty("supportedSignalTypes", NullValueHandling = NullValueHandling.Ignore)] + public eRoutingSignalType? SupportedSignalTypes => _input?.SupportedSignalTypes; + + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name => _input?.Name; + + [JsonProperty("isOnline", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsOnline => _input?.IsOnline.BoolValue; + + [JsonProperty("videoSyncDetected", NullValueHandling = NullValueHandling.Ignore)] + + public bool? VideoSyncDetected => _input?.VideoSyncDetected; + + [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] + public string Key => _input?.Key; + + public RoutingInput(IRoutingInputSlot input) + { + _input = input; + } + } + + /// + /// Represents a RoutingOutput + /// + public class RoutingOutput + { + private IRoutingOutputSlot _output; + + + public RoutingOutput(IRoutingOutputSlot output) + { + _output = output; + } + + [JsonProperty("rxDeviceKey")] + public string RxDeviceKey => _output.RxDeviceKey; + + [JsonProperty("currentRoutes")] + public Dictionary CurrentRoutes => _output.CurrentRoutes.ToDictionary(kvp => kvp.Key.ToString(), kvp => new RoutingInput(kvp.Value)); + + [JsonProperty("slotNumber")] + public int SlotNumber => _output.SlotNumber; + + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + [JsonProperty("supportedSignalTypes")] + public eRoutingSignalType SupportedSignalTypes => _output.SupportedSignalTypes; + + [JsonProperty("name")] + public string Name => _output.Name; + + [JsonProperty("key")] + public string Key => _output.Key; + } + + /// + /// Represents a MatrixRouteRequest + /// + public class MatrixRouteRequest + { + [JsonProperty("outputKey")] + /// + /// Gets or sets the OutputKey + /// + public string OutputKey { get; set; } + + [JsonProperty("inputKey")] + /// + /// Gets or sets the InputKey + /// + public string InputKey { get; set; } + + [JsonProperty("routeType")] + /// + /// Gets or sets the RouteType + /// + public eRoutingSignalType RouteType { get; set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPasswordPromptMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPasswordPromptMessenger.cs deleted file mode 100644 index 84ca2fe3..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPasswordPromptMessenger.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using Newtonsoft.Json; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices implementing - /// - public class IPasswordPromptMessenger : MessengerBase - { - private readonly IPasswordPrompt _device; - - /// - /// Initializes a new instance of the class. - /// - public IPasswordPromptMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _device = device as IPasswordPrompt ?? throw new ArgumentException("device must implement IPasswordPrompt", nameof(device)); - _device.PasswordRequired += OnPasswordRequired; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/password", (id, content) => - { - var msg = content.ToObject>(); - _device.SubmitPassword(msg.Value); - }); - } - - private void OnPasswordRequired(object sender, PasswordPromptEventArgs args) - { - PostEventMessage(new PasswordPromptEventMessage - { - Message = args.Message, - LastAttemptWasIncorrect = args.LastAttemptWasIncorrect, - LoginAttemptFailed = args.LoginAttemptFailed, - LoginAttemptCancelled = args.LoginAttemptCancelled, - EventType = "passwordPrompt" - }); - } - } - - /// - /// Base event message for video codec events - /// - public class VideoCodecBaseEventMessage : DeviceEventMessageBase - { - } - - /// - /// Event message sent when a password is required - /// - public class PasswordPromptEventMessage : VideoCodecBaseEventMessage - { - /// - /// Gets or sets the Message - /// - [JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)] - public string Message { get; set; } - - /// - /// Gets or sets the LastAttemptWasIncorrect - /// - [JsonProperty("lastAttemptWasIncorrect", NullValueHandling = NullValueHandling.Ignore)] - public bool LastAttemptWasIncorrect { get; set; } - - /// - /// Gets or sets the LoginAttemptFailed - /// - [JsonProperty("loginAttemptFailed", NullValueHandling = NullValueHandling.Ignore)] - public bool LoginAttemptFailed { get; set; } - - /// - /// Gets or sets the LoginAttemptCancelled - /// - [JsonProperty("loginAttemptCancelled", NullValueHandling = NullValueHandling.Ignore)] - public bool LoginAttemptCancelled { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs deleted file mode 100644 index 7657606e..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IPrivacyMessenger.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IPrivacyMessenger : MessengerBase - { - private readonly IPrivacy _privacy; - - /// - /// Initializes a new instance of the class. - /// - /// The key for the messenger. - /// The message path for the messenger. - /// The device implementing . - public IPrivacyMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _privacy = device as IPrivacy ?? throw new ArgumentNullException(nameof(device)); - _privacy.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/privacyStatus", (id, content) => SendFullStatus(id)); - - AddAction("/privacyModeOn", (id, content) => _privacy.PrivacyModeOn()); - AddAction("/privacyModeOff", (id, content) => _privacy.PrivacyModeOff()); - AddAction("/privacyModeToggle", (id, content) => _privacy.PrivacyModeToggle()); - } - - private void PrivacyModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) - { - try - { - PostStatusMessage(new IPrivacyStateMessage - { - PrivacyModeIsOn = e.BoolValue - }); - } - catch (Exception ex) - { - this.LogError(ex, "Error posting privacy mode state"); - } - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new IPrivacyStateMessage - { - PrivacyModeIsOn = _privacy.PrivacyModeIsOnFeedback.BoolValue - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending privacy full status"); - } - } - } - - /// - /// State message for - /// - public class IPrivacyStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets a value indicating whether privacy mode is on. Null if unknown or not - /// applicable. - /// - [JsonProperty("privacyModeIsOn", NullValueHandling = NullValueHandling.Ignore)] - public bool? PrivacyModeIsOn { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IProjectorScreenLiftControlMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IProjectorScreenLiftControlMessenger.cs index a02f0f99..f63b4834 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IProjectorScreenLiftControlMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IProjectorScreenLiftControlMessenger.cs @@ -101,4 +101,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public eScreenLiftControlType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoomEventScheduleMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoomEventScheduleMessenger.cs deleted file mode 100644 index 8d5322a0..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoomEventScheduleMessenger.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Room.Config; - -namespace PepperDash.Essentials.AppServer.Messengers; - -/// -/// Represents a RoomEventScheduleMessenger -/// -public class IRoomEventScheduleMessenger : MessengerBase -{ - private readonly IRoomEventSchedule _room; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public IRoomEventScheduleMessenger(string key, string messagePath, IRoomEventSchedule room) - : base(key, messagePath, room as IKeyName) - { - _room = room; - } - - #region Overrides of MessengerBase - - /// - protected override void RegisterActions() - { - AddAction("/saveScheduledEvents", (id, content) => SaveScheduledEvents(content.ToObject>())); - AddAction("/fullStatus", (id, content) => - { - var events = _room.GetScheduledEvents(); - SendFullStatus(events, id); - }); - AddAction("/roomEventStatus", (id, content) => - { - var events = _room.GetScheduledEvents(); - - SendFullStatus(events, id); - }); - - AddAction("/scheduledEventsStatus", (id, content) => - { - var events = _room.GetScheduledEvents(); - - SendFullStatus(events, id); - }); - - _room.ScheduledEventsChanged += (sender, args) => SendFullStatus(args.ScheduledEvents); - } - - #endregion - - private void SaveScheduledEvents(List events) - { - foreach (var evt in events) - { - SaveScheduledEvent(evt); - } - } - - private void SaveScheduledEvent(ScheduledEventConfig eventConfig) - { - try - { - _room.AddOrUpdateScheduledEvent(eventConfig); - } - catch (Exception ex) - { - this.LogException(ex, "Exception saving event"); - } - } - - private void SendFullStatus(List events, string id = null) - { - - var message = new RoomEventScheduleStateMessage - { - ScheduleEvents = events, - }; - - PostStatusMessage(message, id); - } -} - -/// -/// Represents a RoomEventScheduleStateMessage -/// -public class RoomEventScheduleStateMessage : DeviceStateMessageBase -{ - /// - /// Gets or sets the ScheduleEvents - /// - [JsonProperty("scheduleEvents")] - public List ScheduleEvents { get; set; } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoutingMidpointWithFeedbackMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoutingMidpointWithFeedbackMessenger.cs deleted file mode 100644 index 1f5c7789..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRoutingMidpointWithFeedbackMessenger.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.Routing; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Messenger for devices that implement IRoutingMidpointWithFeedback - /// - public class IRoutingMidpointWithFeedbackMessenger : MessengerBase - { - private readonly IRoutingMidpointWithFeedback _device; - - public IRoutingMidpointWithFeedbackMessenger(string key, string messagePath, IRoutingMidpointWithFeedback device) - : base(key, messagePath, device as IKeyName) - { - _device = device; - } - - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/route", (id, content) => - { - var request = content.ToObject(); - _device.ExecuteSwitch(request.InputSelector, request.OutputSelector, request.SignalType); - }); - - AddAction("/clearRoute", (id, content) => - { - var request = content.ToObject(); - _device.ClearRoute(request.OutputSelector, request.SignalType); - }); - - _device.RouteChanged += OnRouteChanged; - } - - private void OnRouteChanged(IRoutingMidpointWithFeedback midpoint, RouteSwitchDescriptor newRoute) - { - PostStatusMessage(JToken.FromObject(new - { - currentRoutes = _device.CurrentRoutes.Select(r => new - { - inputPort = r.InputPort?.Key, - outputPort = r.OutputPort?.Key - }) - })); - } - - private void SendFullStatus(string id = null) - { - var message = JToken.FromObject(new - { - inputPorts = _device.InputPorts.Select(p => new { key = p.Key }), - outputPorts = _device.OutputPorts.Select(p => new { key = p.Key }), - currentRoutes = _device.CurrentRoutes.Select(r => new - { - inputPort = r.InputPort?.Key, - outputPort = r.OutputPort?.Key - }) - }); - - PostStatusMessage(message, id); - } - } - - public class MidpointRouteRequest - { - [JsonProperty("inputSelector")] - public object InputSelector { get; set; } - - [JsonProperty("outputSelector")] - public object OutputSelector { get; set; } - - [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - [JsonProperty("signalType")] - public eRoutingSignalType SignalType { get; set; } - } - - public class MidpointClearRouteRequest - { - [JsonProperty("outputSelector")] - public object OutputSelector { get; set; } - - [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - [JsonProperty("signalType")] - public eRoutingSignalType SignalType { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRunRouteActionMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRunRouteActionMessenger.cs index 42c7c9cd..d5038d91 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRunRouteActionMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IRunRouteActionMessenger.cs @@ -1,5 +1,7 @@ using System; +using Newtonsoft.Json; using PepperDash.Core; +using PepperDash.Core.Logging; using PepperDash.Essentials.Core; @@ -15,22 +17,29 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public IRunRouteAction RoutingDevice { get; private set; } - /// - /// Initializes a new instance of the RunRouteActionMessenger class - /// - /// The key. - /// The routing device. - /// The message path. - /// public RunRouteActionMessenger(string key, IRunRouteAction routingDevice, string messagePath) : base(key, messagePath, routingDevice as IKeyName) { RoutingDevice = routingDevice ?? throw new ArgumentNullException("routingDevice"); + + + if (RoutingDevice is IRoutingSink routingSink) + { + routingSink.CurrentSourceChange += RoutingSink_CurrentSourceChange; + } + } + + private void RoutingSink_CurrentSourceChange(SourceListItem info, ChangeType type) + { + SendRoutingFullMessageObject(); } - /// protected override void RegisterActions() { + AddAction("/fullStatus", (id, content) => SendRoutingFullMessageObject(id)); + + AddAction("/routingStatus", (id, content) => SendRoutingFullMessageObject(id)); + AddAction("/source", (id, content) => { var c = content.ToObject(); @@ -46,7 +55,41 @@ namespace PepperDash.Essentials.AppServer.Messengers RoutingDevice.RunRouteAction(c.SourceListItemKey, sourceListKey); }); + if (RoutingDevice is IRoutingSink sinkDevice) + { + sinkDevice.CurrentSourceChange += (o, a) => SendRoutingFullMessageObject(); + } + } + + /// + /// Helper method to update full status of the routing device + /// + private void SendRoutingFullMessageObject(string id = null) + { + if (RoutingDevice is IRoutingSink sinkDevice) + { + var sourceKey = sinkDevice.CurrentSourceInfoKey; + + if (string.IsNullOrEmpty(sourceKey)) + sourceKey = "none"; + + PostStatusMessage(new RoutingStateMessage + { + SelectedSourceKey = sourceKey + }); + } } } + /// + /// Represents a RoutingStateMessage + /// + public class RoutingStateMessage : DeviceStateMessageBase + { + /// + /// Gets or sets the SelectedSourceKey + /// + [JsonProperty("selectedSourceKey")] + public string SelectedSourceKey { get; set; } + } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISelectableItemsMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISelectableItemsMessenger.cs index df1315a3..8e2f8f3e 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISelectableItemsMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISelectableItemsMessenger.cs @@ -158,4 +158,4 @@ namespace PepperDash.Essentials.AppServer.Messengers public TKey CurrentItem { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShutdownPromptTimerMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShutdownPromptTimerMessenger.cs index 73872b54..516e77c6 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShutdownPromptTimerMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShutdownPromptTimerMessenger.cs @@ -12,22 +12,16 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly IShutdownPromptTimer _room; - /// - /// Create an instance of the class. - /// - /// - /// - /// public IShutdownPromptTimerMessenger(string key, string messagePath, IShutdownPromptTimer room) : base(key, messagePath, room as IKeyName) { _room = room; } - /// protected override void RegisterActions() { - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + AddAction("/status", (id, content) => SendFullStatus(id)); + AddAction("/shutdownPromptStatus", (id, content) => SendFullStatus(id)); AddAction("/setShutdownPromptSeconds", (id, content) => @@ -110,4 +104,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("shutdownPromptSeconds")] public int ShutdownPromptSeconds { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISwitchedOutputMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISwitchedOutputMessenger.cs index ec4acfd8..98223188 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISwitchedOutputMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ISwitchedOutputMessenger.cs @@ -13,20 +13,12 @@ namespace PepperDash.Essentials.AppServer.Messengers private readonly ISwitchedOutput device; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// public ISwitchedOutputMessenger(string key, ISwitchedOutput device, string messagePath) : base(key, messagePath, device as IKeyName) { this.device = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -74,4 +66,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("isOn")] public bool IsOn { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITechPasswordMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITechPasswordMessenger.cs index a2d49cde..283ef0c8 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITechPasswordMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITechPasswordMessenger.cs @@ -11,23 +11,17 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly ITechPassword _room; - /// - /// Constructor for ITechPasswordMessenger - /// - /// - /// - /// public ITechPasswordMessenger(string key, string messagePath, ITechPassword room) : base(key, messagePath, room as IKeyName) { _room = room; } - /// protected override void RegisterActions() { - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + AddAction("/status", (id, content) => SendFullStatus(id)); + AddAction("/techPasswordStatus", (id, content) => SendFullStatus(id)); AddAction("/validateTechPassword", (id, content) => @@ -77,9 +71,6 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class ITechPasswordStateMessage : DeviceStateMessageBase { - /// - /// Gets or sets the TechPasswordLength - /// [JsonProperty("techPasswordLength", NullValueHandling = NullValueHandling.Ignore)] public int? TechPasswordLength { get; set; } } @@ -89,26 +80,23 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class ITechPasswordEventMessage : DeviceEventMessageBase { - /// - /// Gets or sets the IsValid - /// [JsonProperty("isValid", NullValueHandling = NullValueHandling.Ignore)] public bool? IsValid { get; set; } } internal class SetTechPasswordContent { + [JsonProperty("oldPassword")] /// /// Gets or sets the OldPassword /// - [JsonProperty("oldPassword")] public string OldPassword { get; set; } + [JsonProperty("newPassword")] /// /// Gets or sets the NewPassword /// - [JsonProperty("newPassword")] public string NewPassword { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITemperatureSensorMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITemperatureSensorMessenger.cs index e455ed81..5963bba5 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITemperatureSensorMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ITemperatureSensorMessenger.cs @@ -12,20 +12,12 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly ITemperatureSensor device; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// public ITemperatureSensorMessenger(string key, ITemperatureSensor device, string messagePath) : base(key, messagePath, device as IKeyName) { this.device = device; } - - /// protected override void RegisterActions() { base.RegisterActions(); @@ -80,4 +72,4 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("temperatureInCelsius")] public bool TemperatureInCelsius { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs deleted file mode 100644 index 8a8d51fa..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IVideoCodecInfoMessenger.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core; -using PepperDash.Core.Logging; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Devices.Common.Codec; - -namespace PepperDash.Essentials.AppServer.Messengers -{ - /// - /// Provides a messaging bridge for devices implementing - /// - public class IVideoCodecInfoMessenger : MessengerBase - { - private readonly IVideoCodecInfo _codecInfo; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public IVideoCodecInfoMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - _codecInfo = device as IVideoCodecInfo ?? throw new ArgumentNullException(nameof(device)); - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/codecInfoStatus", (id, content) => SendFullStatus(id)); - } - - private void SendFullStatus(string id = null) - { - try - { - var state = new iVideoCodecInfoStateMessage - { - Info = _codecInfo.CodecInfo - }; - - Task.Run(() => PostStatusMessage(state, id)); - } - catch (Exception ex) - { - this.LogError(ex, "Error sending codec info full status"); - } - } - } - - /// - /// State message for - /// - public class iVideoCodecInfoStateMessage : DeviceStateMessageBase - { - /// - /// Gets or sets the codec information. Null if unknown or not applicable. - /// - [JsonProperty("info", NullValueHandling = NullValueHandling.Ignore)] - public VideoCodecInfo Info { get; set; } - } -} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IWarmingCoolingMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IWarmingCoolingMessenger.cs deleted file mode 100644 index 54687e7b..00000000 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IWarmingCoolingMessenger.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Text.Json.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Essentials.Core; - -namespace PepperDash.Essentials.AppServer.Messengers; - -/// -/// Messenger for devices that implement -/// -public class IWarmingCoolingMessenger : MessengerBase -{ - private readonly IWarmingCooling device; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public IWarmingCoolingMessenger(string key, string messagePath, EssentialsDevice device) - : base(key, messagePath, device) - { - this.device = device as IWarmingCooling; - } - - /// - protected override void RegisterActions() - { - base.RegisterActions(); - - AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - - AddAction("/warmingCoolingStatus", (id, content) => SendFullStatus(id)); - - device.IsWarmingUpFeedback.OutputChange += IsWarmingFeedbackOnOutputChange; - device.IsCoolingDownFeedback.OutputChange += IsCoolingFeedbackOnOutputChange; - } - - private void IsWarmingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) - { - PostStatusMessage(JToken.FromObject(new - { - isWarming = feedbackEventArgs.BoolValue - })); - } - - private void IsCoolingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) - { - PostStatusMessage(JToken.FromObject(new - { - isCooling = feedbackEventArgs.BoolValue - })); - } - - private void SendFullStatus(string id = null) - { - var messageObj = new IWarmingCoolingStateMessage - { - IsWarming = device.IsWarmingUpFeedback.BoolValue, - IsCooling = device.IsCoolingDownFeedback.BoolValue - }; - - PostStatusMessage(messageObj, id); - } -} - - -/// -/// Message object for warming/cooling status -/// -public class IWarmingCoolingStateMessage : DeviceStateMessageBase -{ - /// - /// Indicates whether the device is currently warming up. - /// - [JsonProperty("isWarming", NullValueHandling = NullValueHandling.Ignore)] - public bool IsWarming { get; set; } - - /// - /// Indicates whether the device is currently cooling down. - /// - [JsonProperty("isCooling", NullValueHandling = NullValueHandling.Ignore)] - public bool IsCooling { get; set; } -} - diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILightingScenesMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/LightingBaseMessenger.cs similarity index 88% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILightingScenesMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/LightingBaseMessenger.cs index 34f2db13..71c6349f 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ILightingScenesMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/LightingBaseMessenger.cs @@ -13,13 +13,6 @@ namespace PepperDash.Essentials.AppServer.Messengers { private ILightingScenes lightingScenesDevice; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// public ILightingScenesMessenger(string key, ILightingScenes device, string messagePath) : base(key, messagePath, device as IKeyName) { @@ -38,7 +31,6 @@ namespace PepperDash.Essentials.AppServer.Messengers PostStatusMessage(state); } - /// protected override void RegisterActions() { base.RegisterActions(); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs index d61dc6a2..eb3afec3 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/MessengerBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Crestron.SimplSharp.Net; using Newtonsoft.Json.Linq; using PepperDash.Core; using PepperDash.Core.Logging; @@ -12,13 +13,18 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Provides a messaging bridge /// - public abstract class MessengerBase : EssentialsDevice, IMobileControlMessenger + public abstract class MessengerBase : EssentialsDevice, IMobileControlMessengerWithSubscriptions { /// /// The device this messenger is associated with /// protected IKeyName _device; + /// + /// Enable subscriptions + /// + protected bool enableMessengerSubscriptions; + /// /// List of clients subscribed to this messenger /// @@ -78,8 +84,19 @@ namespace PepperDash.Essentials.AppServer.Messengers : this(key, messagePath) { _device = device; + + _deviceInterfaces = GetInterfaces(_device as Device); } + /// + /// Gets the interfaces implmented on the device + /// + /// + /// + private List GetInterfaces(Device device) + { + return device?.GetType().GetInterfaces().Select((i) => i.Name).ToList() ?? new List(); + } /// /// Registers this messenger with appserver controller @@ -94,6 +111,21 @@ namespace PepperDash.Essentials.AppServer.Messengers RegisterActions(); } + /// + /// Register this messenger with appserver controller + /// + /// Parent controller for this messenger + /// Enable subscriptions + public void RegisterWithAppServer(IMobileControl appServerController, bool enableMessengerSubscriptions) + { + this.enableMessengerSubscriptions = enableMessengerSubscriptions; + AppServerController = appServerController ?? throw new ArgumentNullException("appServerController"); + + AppServerController.AddAction(this, HandleMessage); + + RegisterActions(); + } + private void HandleMessage(string path, string id, JToken content) { // replace base path with empty string. Should leave something like /fullStatus @@ -104,12 +136,6 @@ namespace PepperDash.Essentials.AppServer.Messengers return; } - // Auto-subscribe the client on any incoming action - if (!string.IsNullOrEmpty(id)) - { - SubscribeClient(id); - } - this.LogDebug("Executing action for path {path}", path); action(id, content); @@ -161,15 +187,21 @@ namespace PepperDash.Essentials.AppServer.Messengers } /// - /// Add client to the subscription list for unsolicited feedback + /// Add client to the susbscription list for unsolicited feedback /// /// Client ID to add - private void SubscribeClient(string clientId) + protected void SubscribeClient(string clientId) { + if (!enableMessengerSubscriptions) + { + return; + } + lock (_subscriberLock) { if (!subscriberIds.Add(clientId)) { + this.LogVerbose("Client {clientId} already subscribed", clientId); return; } } @@ -183,14 +215,24 @@ namespace PepperDash.Essentials.AppServer.Messengers /// Client ID to remove public void UnsubscribeClient(string clientId) { + if (!enableMessengerSubscriptions) + { + return; + } + bool wasSubscribed; lock (_subscriberLock) { - wasSubscribed = subscriberIds.Remove(clientId); + wasSubscribed = subscriberIds.Contains(clientId); + if (wasSubscribed) + { + subscriberIds.Remove(clientId); + } } if (!wasSubscribed) { + this.LogVerbose("Client with ID {clientId} is not subscribed", clientId); return; } @@ -216,12 +258,15 @@ namespace PepperDash.Essentials.AppServer.Messengers throw new ArgumentNullException("device"); } + message.SetInterfaces(_deviceInterfaces); + message.Key = _device.Key; message.Name = _device.Name; message.MessageBasePath = MessagePath; + var token = JToken.FromObject(message); PostStatusMessage(token, MessagePath, clientId); @@ -243,6 +288,9 @@ namespace PepperDash.Essentials.AppServer.Messengers { try { + //Debug.Console(2, this, "*********************Setting DeviceStateMessageProperties on MobileControlResponseMessage"); + deviceState.SetInterfaces(_deviceInterfaces); + deviceState.Key = _device.Key; deviceState.Name = _device.Name; @@ -270,26 +318,35 @@ namespace PepperDash.Essentials.AppServer.Messengers { try { - var messageType = !string.IsNullOrEmpty(type) ? type : MessagePath; - - // If clientId is provided, send directly to that client - if (!string.IsNullOrEmpty(clientId)) + // Allow for legacy method to continue without subscriptions + if (!enableMessengerSubscriptions) { - AppServerController?.SendMessageObject(new MobileControlMessage { Type = messageType, ClientId = clientId, Content = content }); + AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = clientId, Content = content }); return; } - // Unsolicited feedback: send to all subscribers - List subscriberSnapshot; - lock (_subscriberLock) + // handle subscription feedback + // If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties + if (string.IsNullOrEmpty(clientId)) { - subscriberSnapshot = new List(subscriberIds); + // Create a snapshot of subscribers to avoid collection modification during iteration + List subscriberSnapshot; + lock (_subscriberLock) + { + subscriberSnapshot = new List(subscriberIds); + } + + foreach (var client in subscriberSnapshot) + { + AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content }); + } + + return; } - foreach (var client in subscriberSnapshot) - { - AppServerController?.SendMessageObject(new MobileControlMessage { Type = messageType, ClientId = client, Content = content }); - } + SubscribeClient(clientId); + + AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = clientId, Content = content }); } catch (Exception ex) { @@ -349,4 +406,4 @@ namespace PepperDash.Essentials.AppServer.Messengers } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/PressAndHoldHandler.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/PressAndHoldHandler.cs index 6256142a..b076566b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/PressAndHoldHandler.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/PressAndHoldHandler.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Timers; +using Crestron.SimplSharp; using Newtonsoft.Json.Linq; using PepperDash.Core; @@ -13,7 +13,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { private const long ButtonHeartbeatInterval = 1000; - private static readonly Dictionary _pushedActions = new Dictionary(); + private static readonly Dictionary _pushedActions = new Dictionary(); private static readonly Dictionary>> _pushedActionHandlers; @@ -31,7 +31,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { Debug.LogDebug("Attempting to add timer for {deviceKey}", deviceKey); - if (_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer)) + if (_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) { Debug.LogDebug("Timer for {deviceKey} already exists", deviceKey); return; @@ -41,16 +41,14 @@ namespace PepperDash.Essentials.AppServer.Messengers action(true); - cancelTimer = new Timer(ButtonHeartbeatInterval) { AutoReset = false }; - cancelTimer.Elapsed += (s, e) => + cancelTimer = new CTimer(o => { Debug.LogDebug("Timer expired for {deviceKey}", deviceKey); action(false); _pushedActions.Remove(deviceKey); - }; - cancelTimer.Start(); + }, ButtonHeartbeatInterval); _pushedActions.Add(deviceKey, cancelTimer); } @@ -59,7 +57,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { Debug.LogDebug("Attempting to reset timer for {deviceKey}", deviceKey); - if (!_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer)) + if (!_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) { Debug.LogDebug("Timer for {deviceKey} not found", deviceKey); return; @@ -67,16 +65,14 @@ namespace PepperDash.Essentials.AppServer.Messengers Debug.LogDebug("Resetting timer for {deviceKey} with due time {dueTime}", deviceKey, ButtonHeartbeatInterval); - cancelTimer.Stop(); - cancelTimer.Interval = ButtonHeartbeatInterval; - cancelTimer.Start(); + cancelTimer.Reset(ButtonHeartbeatInterval); } private static void StopTimer(string deviceKey, Action action) { Debug.LogDebug("Attempting to stop timer for {deviceKey}", deviceKey); - if (!_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer)) + if (!_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) { Debug.LogDebug("Timer for {deviceKey} not found", deviceKey); return; @@ -89,11 +85,6 @@ namespace PepperDash.Essentials.AppServer.Messengers _pushedActions.Remove(deviceKey); } - /// - /// Gets the handler for a given press and hold message type - /// - /// The press and hold message type. - /// The handler for the specified message type. public static Action> GetPressAndHoldHandler(string value) { Debug.LogDebug("Getting press and hold handler for {value}", value); @@ -128,4 +119,4 @@ namespace PepperDash.Essentials.AppServer.Messengers timerHandler(deviceKey, action); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/RoomEventScheduleMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/RoomEventScheduleMessenger.cs new file mode 100644 index 00000000..2e10d0f3 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/RoomEventScheduleMessenger.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using PepperDash.Core; +using PepperDash.Core.Logging; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Room.Config; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Represents a RoomEventScheduleMessenger + /// + public class RoomEventScheduleMessenger : MessengerBase + { + private readonly IRoomEventSchedule _room; + + + public RoomEventScheduleMessenger(string key, string messagePath, IRoomEventSchedule room) + : base(key, messagePath, room as IKeyName) + { + _room = room; + } + + #region Overrides of MessengerBase + + protected override void RegisterActions() + { + AddAction("/saveScheduledEvents", (id, content) => SaveScheduledEvents(content.ToObject>())); + AddAction("/status", (id, content) => + { + var events = _room.GetScheduledEvents(); + + SendFullStatus(events, id); + }); + + AddAction("/scheduledEventsStatus", (id, content) => + { + var events = _room.GetScheduledEvents(); + + SendFullStatus(events, id); + }); + + _room.ScheduledEventsChanged += (sender, args) => SendFullStatus(args.ScheduledEvents); + } + + #endregion + + private void SaveScheduledEvents(List events) + { + foreach (var evt in events) + { + SaveScheduledEvent(evt); + } + } + + private void SaveScheduledEvent(ScheduledEventConfig eventConfig) + { + try + { + _room.AddOrUpdateScheduledEvent(eventConfig); + } + catch (Exception ex) + { + this.LogException(ex, "Exception saving event"); + } + } + + private void SendFullStatus(List events, string id = null) + { + + var message = new RoomEventScheduleStateMessage + { + ScheduleEvents = events, + }; + + PostStatusMessage(message, id); + } + } + + /// + /// Represents a RoomEventScheduleStateMessage + /// + public class RoomEventScheduleStateMessage : DeviceStateMessageBase + { + [JsonProperty("scheduleEvents")] + /// + /// Gets or sets the ScheduleEvents + /// + public List ScheduleEvents { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLDirectRouteMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLDirectRouteMessenger.cs index 829f66ee..e4a5d2e7 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLDirectRouteMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLDirectRouteMessenger.cs @@ -33,7 +33,7 @@ namespace PepperDash.Essentials.AppServer.Messengers protected override void RegisterActions() { - this.LogDebug("Direct Route Messenger CustomRegisterWithAppServer **********"); + Debug.Console(2, "********** Direct Route Messenger CustomRegisterWithAppServer **********"); //Audio source @@ -81,7 +81,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { var b = content.ToObject>(); - this.LogDebug("Current Sharing Mode: {2}\r\nadvanced sharing mode: {0} join number: {1}", b.Value, + Debug.Console(1, "Current Sharing Mode: {2}\r\nadvanced sharing mode: {0} join number: {1}", b.Value, JoinMap.AdvancedSharingModeOn.JoinNumber, _eisc.BooleanOutput[JoinMap.AdvancedSharingModeOn.JoinNumber].BoolValue); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLVtcMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLVtcMessenger.cs index 0dc713bb..6c5857e9 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLVtcMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SIMPLVtcMessenger.cs @@ -255,10 +255,10 @@ namespace PepperDash.Essentials.AppServer.Messengers _eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u); _eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber); } - catch (Exception e) + catch (Exception) { - this.LogException(e,"directoryById request contains non-numeric ID incompatible with SIMPL bridge: {0}", e.Message); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, + "/directoryById request contains non-numeric ID incompatible with SIMPL bridge"); } }); AddAction("/directorySelectContact", (id, content) => @@ -270,10 +270,9 @@ namespace PepperDash.Essentials.AppServer.Messengers _eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u); _eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber); } - catch (Exception e) + catch { - this.LogException(e, "Error parsing contact from {0} for path /directorySelectContact", s.Value); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); + Debug.Console(2, this, "Error parsing contact from {0} for path /directorySelectContact", s); } }); AddAction("/directoryDialContact", diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShadesOpenCloseStopMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ShadeBaseMessenger.cs similarity index 87% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShadesOpenCloseStopMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ShadeBaseMessenger.cs index dc5cbe6a..5492cc2b 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IShadesOpenCloseStopMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/ShadeBaseMessenger.cs @@ -12,19 +12,12 @@ namespace PepperDash.Essentials.AppServer.Messengers { private readonly IShadesOpenCloseStop device; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// public IShadesOpenCloseStopMessenger(string key, IShadesOpenCloseStop shades, string messagePath) : base(key, messagePath, shades as IKeyName) { device = shades; } - /// protected override void RegisterActions() { base.RegisterActions(); @@ -109,16 +102,9 @@ namespace PepperDash.Essentials.AppServer.Messengers [JsonProperty("middleButtonLabel", NullValueHandling = NullValueHandling.Ignore)] public string MiddleButtonLabel { get; set; } - /// - /// Gets or sets the IsOpen - /// [JsonProperty("isOpen", NullValueHandling = NullValueHandling.Ignore)] public bool? IsOpen { get; set; } - - /// - /// Gets or sets the IsClosed - /// [JsonProperty("isClosed", NullValueHandling = NullValueHandling.Ignore)] public bool? IsClosed { get; set; } } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SystemMonitorMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SystemMonitorMessenger.cs index 6cf3a5ad..63869ae0 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SystemMonitorMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/SystemMonitorMessenger.cs @@ -40,6 +40,7 @@ namespace PepperDash.Essentials.AppServer.Messengers { if (e.ProgramInfo != null) { + //Debug.Console(1, "Posting Status Message: {0}", e.ProgramInfo.ToString()); PostStatusMessage(JToken.FromObject(e.ProgramInfo) ); } @@ -81,7 +82,6 @@ namespace PepperDash.Essentials.AppServer.Messengers )); } - /// protected override void RegisterActions() { AddAction("/fullStatus", (id, content) => SendFullStatusMessage(id)); diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDisplayCurrentInputMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/TwoWayDisplayBaseMessenger.cs similarity index 56% rename from src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDisplayCurrentInputMessenger.cs rename to src/PepperDash.Essentials.MobileControl.Messengers/Messengers/TwoWayDisplayBaseMessenger.cs index 0239ecf9..5147da4d 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/IDisplayCurrentInputMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/TwoWayDisplayBaseMessenger.cs @@ -1,27 +1,19 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using PepperDash.Core; using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Devices.Common.Displays; namespace PepperDash.Essentials.AppServer.Messengers { /// - /// Represents a messenger for a display device that has current input information. + /// Represents a TwoWayDisplayBaseMessenger /// - public class IDisplayCurrentInputMessenger : MessengerBase + public class TwoWayDisplayBaseMessenger : MessengerBase { - private readonly IDisplayCurrentInput _display; + private readonly TwoWayDisplayBase _display; - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public IDisplayCurrentInputMessenger(string key, string messagePath, IDisplayCurrentInput display) - : base(key, messagePath, display as IKeyName) + public TwoWayDisplayBaseMessenger(string key, string messagePath, TwoWayDisplayBase display) + : base(key, messagePath, display) { _display = display; } @@ -33,24 +25,26 @@ namespace PepperDash.Essentials.AppServer.Messengers /// public void SendFullStatus(string id = null) { - var messageObj = new CurrentInputStateMessage + var messageObj = new TwoWayDisplayBaseStateMessage { + //PowerState = _display.PowerIsOnFeedback.BoolValue, CurrentInput = _display.CurrentInputFeedback.StringValue }; PostStatusMessage(messageObj, id); } - /// protected override void RegisterActions() { base.RegisterActions(); AddAction("/fullStatus", (id, content) => SendFullStatus(id)); - AddAction("/currentInputStatus", (id, content) => SendFullStatus(id)); + AddAction("/displayStatus", (id, content) => SendFullStatus(id)); _display.CurrentInputFeedback.OutputChange += CurrentInputFeedbackOnOutputChange; + _display.IsCoolingDownFeedback.OutputChange += IsCoolingFeedbackOnOutputChange; + _display.IsWarmingUpFeedback.OutputChange += IsWarmingFeedbackOnOutputChange; } private void CurrentInputFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) @@ -60,6 +54,24 @@ namespace PepperDash.Essentials.AppServer.Messengers currentInput = feedbackEventArgs.StringValue }) ); + } + + private void IsWarmingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) + { + PostStatusMessage(JToken.FromObject(new + { + isWarming = feedbackEventArgs.BoolValue + }) + ); + } + + private void IsCoolingFeedbackOnOutputChange(object sender, FeedbackEventArgs feedbackEventArgs) + { + PostStatusMessage(JToken.FromObject(new + { + isCooling = feedbackEventArgs.BoolValue + }) + ); } @@ -70,7 +82,7 @@ namespace PepperDash.Essentials.AppServer.Messengers /// /// Represents a TwoWayDisplayBaseStateMessage /// - public class CurrentInputStateMessage : DeviceStateMessageBase + public class TwoWayDisplayBaseStateMessage : DeviceStateMessageBase { //[JsonProperty("powerState", NullValueHandling = NullValueHandling.Ignore)] //public bool? PowerState { get; set; } diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs new file mode 100644 index 00000000..071eaf27 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl.Messengers/Messengers/VideoCodecBaseMessenger.cs @@ -0,0 +1,1185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core; +using PepperDash.Core.Logging; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.DeviceTypeInterfaces; +using PepperDash.Essentials.Devices.Common.Cameras; +using PepperDash.Essentials.Devices.Common.Codec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; + +namespace PepperDash.Essentials.AppServer.Messengers +{ + /// + /// Provides a messaging bridge for a VideoCodecBase device + /// + public class VideoCodecBaseMessenger : MessengerBase + { + /// + /// + /// + protected VideoCodecBase Codec { get; private set; } + + + + /// + /// + /// + /// + /// + /// + public VideoCodecBaseMessenger(string key, VideoCodecBase codec, string messagePath) + : base(key, messagePath, codec) + { + Codec = codec ?? throw new ArgumentNullException("codec"); + codec.CallStatusChange += Codec_CallStatusChange; + codec.IsReadyChange += Codec_IsReadyChange; + + if (codec is IHasDirectory dirCodec) + { + dirCodec.DirectoryResultReturned += DirCodec_DirectoryResultReturned; + } + + if (codec is IHasCallHistory recCodec) + { + recCodec.CallHistory.RecentCallsListHasChanged += CallHistory_RecentCallsListHasChanged; + } + + if (codec is IPasswordPrompt pwPromptCodec) + { + pwPromptCodec.PasswordRequired += OnPasswordRequired; + } + } + + private void OnPasswordRequired(object sender, PasswordPromptEventArgs args) + { + var eventMsg = new PasswordPromptEventMessage + { + Message = args.Message, + LastAttemptWasIncorrect = args.LastAttemptWasIncorrect, + LoginAttemptFailed = args.LoginAttemptFailed, + LoginAttemptCancelled = args.LoginAttemptCancelled, + EventType = "passwordPrompt" + }; + + PostEventMessage(eventMsg); + } + + /// + /// + /// + /// + /// + private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e) + { + try + { + var state = new VideoCodecBaseStateMessage(); + + if (!(sender is CodecCallHistory codecCallHistory)) return; + var recents = codecCallHistory.RecentCalls; + + if (recents != null) + { + state.RecentCalls = recents; + + PostStatusMessage(state); + } + } + catch (Exception ex) + { + this.LogError(ex, "Error posting call history"); + } + } + + /// + /// + /// + /// + /// + protected virtual void DirCodec_DirectoryResultReturned(object sender, DirectoryEventArgs e) + { + if (Codec is IHasDirectory) + SendDirectory(e.Directory); + } + + /// + /// Posts the current directory + /// + protected void SendDirectory(CodecDirectory directory) + { + try + { + var state = new VideoCodecBaseStateMessage(); + + + if (Codec is IHasDirectory dirCodec) + { + this.LogVerbose("Sending Directory. Directory Item Count: {directoryItemCount}", directory.CurrentDirectoryResults.Count); + + //state.CurrentDirectory = PrefixDirectoryFolderItems(directory); + state.CurrentDirectory = directory; + + CrestronInvoke.BeginInvoke((o) => PostStatusMessage(state)); + } + } + catch (Exception ex) + { + this.LogError(ex, "Error sending directory"); + } + } + + /// + /// + /// + /// + /// + private void Codec_IsReadyChange(object sender, EventArgs e) + { + try + { + var state = new VideoCodecBaseStateMessage + { + IsReady = true + }; + + PostStatusMessage(state); + + SendFullStatus(); + } + catch (Exception ex) + { + this.LogError(ex, "Error sending codec ready status"); + } + } + + /// + /// Called from base's RegisterWithAppServer method + /// + /// + protected override void RegisterActions() + { + try + { + base.RegisterActions(); + + AddAction("/isReady", (id, content) => SendIsReady()); + + AddAction("/fullStatus", (id, content) => SendFullStatus(id)); + AddAction("/codecStatus", (id, content) => SendFullStatus(id)); + + AddAction("/dial", (id, content) => + { + var value = content.ToObject>(); + + Codec.Dial(value.Value); + }); + + AddAction("/dialMeeting", (id, content) => Codec.Dial(content.ToObject())); + + AddAction("/endCallById", (id, content) => + { + var s = content.ToObject>(); + var call = GetCallWithId(s.Value); + if (call != null) + Codec.EndCall(call); + }); + + AddAction("/endAllCalls", (id, content) => Codec.EndAllCalls()); + + AddAction("/dtmf", (id, content) => + { + var s = content.ToObject>(); + Codec.SendDtmf(s.Value); + }); + + AddAction("/rejectById", (id, content) => + { + var s = content.ToObject>(); + + var call = GetCallWithId(s.Value); + if (call != null) + Codec.RejectCall(call); + }); + + AddAction("/acceptById", (id, content) => + { + var s = content.ToObject>(); + + var call = GetCallWithId(s.Value); + if (call != null) + Codec.AcceptCall(call); + }); + + Codec.SharingContentIsOnFeedback.OutputChange += SharingContentIsOnFeedback_OutputChange; + Codec.SharingSourceFeedback.OutputChange += SharingSourceFeedback_OutputChange; + + // Directory actions + if (Codec is IHasDirectory dirCodec) + { + AddAction("/getDirectory", (id, content) => GetDirectoryRoot()); + + AddAction("/directoryById", (id, content) => + { + var msg = content.ToObject>(); + GetDirectory(msg.Value); + }); + + AddAction("/directorySearch", (id, content) => + { + var msg = content.ToObject>(); + + GetDirectory(msg.Value); + }); + + AddAction("/directoryBack", (id, content) => GetPreviousDirectory()); + + dirCodec.PhonebookSyncState.InitialSyncCompleted += PhonebookSyncState_InitialSyncCompleted; + } + + // History actions + if (Codec is IHasCallHistory recCodec) + { + AddAction("/getCallHistory", (id, content) => PostCallHistory()); + } + if (Codec is IHasCodecCameras cameraCodec) + { + this.LogVerbose("Adding IHasCodecCameras Actions"); + + cameraCodec.CameraSelected += CameraCodec_CameraSelected; + + AddAction("/cameraSelect", (id, content) => + { + var msg = content.ToObject>(); + + cameraCodec.SelectCamera(msg.Value); + }); + + + MapCameraActions(); + + if (Codec is IHasCodecRoomPresets presetsCodec) + { + this.LogVerbose("Adding IHasCodecRoomPresets Actions"); + + presetsCodec.CodecRoomPresetsListHasChanged += PresetsCodec_CameraPresetsListHasChanged; + + AddAction("/cameraPreset", (id, content) => + { + var msg = content.ToObject>(); + + presetsCodec.CodecRoomPresetSelect(msg.Value); + }); + + AddAction("/cameraPresetStore", (id, content) => + { + var msg = content.ToObject(); + + presetsCodec.CodecRoomPresetStore(msg.ID, msg.Description); + }); + } + + if (Codec is IHasCameraAutoMode speakerTrackCodec) + { + this.LogVerbose("Adding IHasCameraAutoMode Actions"); + + speakerTrackCodec.CameraAutoModeIsOnFeedback.OutputChange += CameraAutoModeIsOnFeedback_OutputChange; + + AddAction("/cameraModeAuto", (id, content) => speakerTrackCodec.CameraAutoModeOn()); + + AddAction("/cameraModeManual", (id, content) => speakerTrackCodec.CameraAutoModeOff()); + } + + if (Codec is IHasCameraOff cameraOffCodec) + { + this.LogVerbose("Adding IHasCameraOff Actions"); + + cameraOffCodec.CameraIsOffFeedback.OutputChange += (CameraIsOffFeedback_OutputChange); + + AddAction("/cameraModeOff", (id, content) => cameraOffCodec.CameraOff()); + } + } + + + + if (Codec is IHasCodecSelfView selfViewCodec) + { + this.LogVerbose("Adding IHasCodecSelfView Actions"); + + AddAction("/cameraSelfView", (id, content) => selfViewCodec.SelfViewModeToggle()); + + selfViewCodec.SelfviewIsOnFeedback.OutputChange += new EventHandler(SelfviewIsOnFeedback_OutputChange); + } + + + if (Codec is IHasCodecLayouts layoutsCodec) + { + this.LogVerbose("Adding IHasCodecLayouts Actions"); + + AddAction("/cameraRemoteView", (id, content) => layoutsCodec.LocalLayoutToggle()); + + AddAction("/cameraLayout", (id, content) => layoutsCodec.LocalLayoutToggle()); + } + + if (Codec is IPasswordPrompt pwCodec) + { + this.LogVerbose("Adding IPasswordPrompt Actions"); + + AddAction("/password", (id, content) => + { + var msg = content.ToObject>(); + + pwCodec.SubmitPassword(msg.Value); + }); + } + + + if (Codec is IHasFarEndContentStatus farEndContentStatus) + { + farEndContentStatus.ReceivingContent.OutputChange += + (sender, args) => PostReceivingContent(args.BoolValue); + } + + this.LogVerbose("Adding Privacy & Standby Actions"); + + AddAction("/privacyModeOn", (id, content) => Codec.PrivacyModeOn()); + AddAction("/privacyModeOff", (id, content) => Codec.PrivacyModeOff()); + AddAction("/privacyModeToggle", (id, content) => Codec.PrivacyModeToggle()); + AddAction("/sharingStart", (id, content) => Codec.StartSharing()); + AddAction("/sharingStop", (id, content) => Codec.StopSharing()); + AddAction("/standbyOn", (id, content) => Codec.StandbyActivate()); + AddAction("/standbyOff", (id, content) => Codec.StandbyDeactivate()); + } + catch (Exception e) + { + this.LogException(e, "Exception adding paths"); + } + } + + private void SharingSourceFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + try + { + var state = new VideoCodecBaseStateMessage + { + SharingSource = e.StringValue + }; + + PostStatusMessage(state); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting sharing source"); + } + } + + private void SharingContentIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + try + { + var state = new VideoCodecBaseStateMessage + { + SharingContentIsOn = e.BoolValue + }; + + PostStatusMessage(state); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting sharing content"); + } + } + + private void PhonebookSyncState_InitialSyncCompleted(object sender, EventArgs e) + { + try + { + var state = new VideoCodecBaseStateMessage + { + InitialPhonebookSyncComplete = true + }; + + PostStatusMessage(state); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting phonebook sync state"); + } + } + + private void CameraIsOffFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostCameraMode(); + } + + private void SelfviewIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostCameraSelfView(); + } + + private void PresetsCodec_CameraPresetsListHasChanged(object sender, EventArgs e) + { + PostCameraPresets(); + } + + private void CameraAutoModeIsOnFeedback_OutputChange(object sender, FeedbackEventArgs e) + { + PostCameraMode(); + } + + + private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs e) + { + try + { + MapCameraActions(); + PostSelectedCamera(); + } + catch (Exception ex) + { + this.LogError(ex, "Exception handling camera selected event"); + } + } + + /// + /// Maps the camera control actions to the current selected camera on the codec + /// + private void MapCameraActions() + { + if (Codec is IHasCameras cameraCodec && cameraCodec.SelectedCamera != null) + { + RemoveAction("/cameraUp"); + RemoveAction("/cameraDown"); + RemoveAction("/cameraLeft"); + RemoveAction("/cameraRight"); + RemoveAction("/cameraZoomIn"); + RemoveAction("/cameraZoomOut"); + RemoveAction("/cameraHome"); + + if (cameraCodec.SelectedCamera is IHasCameraPtzControl camera) + { + AddAction("/cameraUp", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.TiltUp(); + return; + } + + camera.TiltStop(); + })); + + AddAction("/cameraDown", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.TiltDown(); + return; + } + + camera.TiltStop(); + })); + + AddAction("/cameraLeft", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.PanLeft(); + return; + } + + camera.PanStop(); + })); + + AddAction("/cameraRight", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.PanRight(); + return; + } + + camera.PanStop(); + })); + + AddAction("/cameraZoomIn", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.ZoomIn(); + return; + } + + camera.ZoomStop(); + })); + + AddAction("/cameraZoomOut", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + camera.ZoomOut(); + return; + } + + camera.ZoomStop(); + })); + AddAction("/cameraHome", (id, content) => camera.PositionHome()); + + + RemoveAction("/cameraAutoFocus"); + RemoveAction("/cameraFocusNear"); + RemoveAction("/cameraFocusFar"); + + if (cameraCodec is IHasCameraFocusControl focusCamera) + { + AddAction("/cameraAutoFocus", (id, content) => focusCamera.TriggerAutoFocus()); + + AddAction("/cameraFocusNear", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + focusCamera.FocusNear(); + return; + } + + focusCamera.FocusStop(); + })); + + AddAction("/cameraFocusFar", (id, content) => HandleCameraPressAndHold(content, (b) => + { + if (b) + { + focusCamera.FocusFar(); + return; + } + + focusCamera.FocusStop(); + })); + } + } + } + } + + private void HandleCameraPressAndHold(JToken content, Action cameraAction) + { + var state = content.ToObject>(); + + var timerHandler = PressAndHoldHandler.GetPressAndHoldHandler(state.Value); + if (timerHandler == null) + { + return; + } + + timerHandler(state.Value, cameraAction); + + cameraAction(state.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase)); + } + + private string GetCameraMode() + { + string m = ""; + + if (Codec is IHasCameraAutoMode speakerTrackCodec) + { + m = speakerTrackCodec.CameraAutoModeIsOnFeedback.BoolValue + ? eCameraControlMode.Auto.ToString().ToLower() + : eCameraControlMode.Manual.ToString().ToLower(); + } + + if (Codec is IHasCameraOff cameraOffCodec) + { + if (cameraOffCodec.CameraIsOffFeedback.BoolValue) + m = eCameraControlMode.Off.ToString().ToLower(); + } + + return m; + } + + private void PostCallHistory() + { + try + { + var codec = (Codec as IHasCallHistory); + + if (codec != null) + { + var status = new VideoCodecBaseStateMessage(); + + var recents = codec.CallHistory.RecentCalls; + + if (recents != null) + { + status.RecentCalls = codec.CallHistory.RecentCalls; + + PostStatusMessage(status); + } + } + } + catch (Exception ex) + { + this.LogError(ex, "Error posting call history"); + } + } + + /// + /// Helper to grab a call with string ID + /// + /// + /// + private CodecActiveCallItem GetCallWithId(string id) + { + return Codec.ActiveCalls.FirstOrDefault(c => c.Id == id); + } + + /// + /// + /// + /// + private void GetDirectory(string id) + { + if (!(Codec is IHasDirectory dirCodec)) + { + return; + } + dirCodec.GetDirectoryFolderContents(id); + } + + /// + /// + /// + private void GetDirectoryRoot() + { + try + { + if (!(Codec is IHasDirectory dirCodec)) + { + // do something else? + return; + } + if (!dirCodec.PhonebookSyncState.InitialSyncComplete) + { + var state = new VideoCodecBaseStateMessage + { + InitialPhonebookSyncComplete = false + }; + + PostStatusMessage(state); + return; + } + + dirCodec.SetCurrentDirectoryToRoot(); + } + catch (Exception ex) + { + this.LogError(ex, "Error getting directory root"); + } + } + + /// + /// Requests the parent folder contents + /// + private void GetPreviousDirectory() + { + if (!(Codec is IHasDirectory dirCodec)) + { + return; + } + + dirCodec.GetDirectoryParentFolderContents(); + } + + /// + /// Handler for codec changes + /// + private void Codec_CallStatusChange(object sender, CodecCallStatusItemChangeEventArgs e) + { + SendFullStatus(); + } + + /// + /// + /// + private void SendIsReady() + { + try + { + var status = new VideoCodecBaseStateMessage(); + + var codecType = Codec.GetType(); + + status.IsReady = Codec.IsReady; + status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null; + + PostStatusMessage(status); + } + catch (Exception ex) + { + this.LogError(ex, "Error sending codec ready status"); + } + } + + /// + /// Helper method to build call status for vtc + /// + /// + protected VideoCodecBaseStateMessage GetStatus() + { + try + { + var status = new VideoCodecBaseStateMessage(); + + if (Codec is IHasCodecCameras camerasCodec) + { + status.Cameras = new CameraStatus + { + CameraManualIsSupported = true, + CameraAutoIsSupported = Codec.SupportsCameraAutoMode, + CameraOffIsSupported = Codec.SupportsCameraOff, + CameraMode = GetCameraMode(), + Cameras = camerasCodec.Cameras, + SelectedCamera = GetSelectedCamera(camerasCodec) + }; + } + + if (Codec is IHasDirectory directoryCodec) + { + status.HasDirectory = true; + status.HasDirectorySearch = true; + status.CurrentDirectory = directoryCodec.CurrentDirectoryResult; + } + + var codecType = Codec.GetType(); + + status.CameraSelfViewIsOn = Codec is IHasCodecSelfView && (Codec as IHasCodecSelfView).SelfviewIsOnFeedback.BoolValue; + status.IsInCall = Codec.IsInCall; + status.PrivacyModeIsOn = Codec.PrivacyModeIsOnFeedback.BoolValue; + status.SharingContentIsOn = Codec.SharingContentIsOnFeedback.BoolValue; + status.SharingSource = Codec.SharingSourceFeedback.StringValue; + status.StandbyIsOn = Codec.StandbyIsOnFeedback.BoolValue; + status.Calls = Codec.ActiveCalls; + status.Info = Codec.CodecInfo; + status.ShowSelfViewByDefault = Codec.ShowSelfViewByDefault; + status.SupportsAdHocMeeting = Codec is IHasStartMeeting; + status.HasRecents = Codec is IHasCallHistory; + status.HasCameras = Codec is IHasCameras; + status.Presets = GetCurrentPresets(); + status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null; + status.ReceivingContent = Codec is IHasFarEndContentStatus && (Codec as IHasFarEndContentStatus).ReceivingContent.BoolValue; + + if (Codec is IHasMeetingInfo meetingInfoCodec) + { + status.MeetingInfo = meetingInfoCodec.MeetingInfo; + } + + return status; + } + catch (Exception ex) + { + this.LogError(ex, "Error getting codec status"); + return null; + } + } + + protected virtual void SendFullStatus(string id = null) + { + if (!Codec.IsReady) + { + return; + } + + Task.Run(() => PostStatusMessage(GetStatus(), id)); + } + + private void PostReceivingContent(bool receivingContent) + { + try + { + var state = new VideoCodecBaseStateMessage + { + ReceivingContent = receivingContent + }; + + PostStatusMessage(state); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting receiving content"); + } + } + + private void PostCameraSelfView() + { + try + { + var status = new VideoCodecBaseStateMessage + { + CameraSelfViewIsOn = Codec is IHasCodecSelfView + && (Codec as IHasCodecSelfView).SelfviewIsOnFeedback.BoolValue + }; + + PostStatusMessage(status); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting camera self view"); + } + } + + /// + /// + /// + private void PostCameraMode() + { + try + { + var status = new VideoCodecBaseStateMessage + { + CameraMode = GetCameraMode() + }; + + PostStatusMessage(status); + } + catch (Exception ex) + { + this.LogError(ex, "Error posting camera mode"); + } + } + + private void PostSelectedCamera() + { + try + { + var camerasCodec = Codec as IHasCodecCameras; + + var status = new VideoCodecBaseStateMessage + { + Cameras = new CameraStatus() { SelectedCamera = GetSelectedCamera(camerasCodec) }, + Presets = GetCurrentPresets() + }; + + PostStatusMessage(status); + } + catch (Exception e) + { + this.LogError(e, "Error posting selected camera"); + } + } + + private void PostCameraPresets() + { + try + { + var status = new VideoCodecBaseStateMessage + { + Presets = GetCurrentPresets() + }; + + PostStatusMessage(status); + } + catch (Exception e) + { + this.LogError(e, "Error posting camera presets"); + } + } + + private Camera GetSelectedCamera(IHasCodecCameras camerasCodec) + { + var camera = new Camera(); + + if (camerasCodec.SelectedCameraFeedback != null) + camera.Key = camerasCodec.SelectedCameraFeedback.StringValue; + if (camerasCodec.SelectedCamera != null) + { + camera.Name = camerasCodec.SelectedCamera.Name; + + camera.Capabilities = new CameraCapabilities() + { + CanPan = camerasCodec.SelectedCamera.CanPan, + CanTilt = camerasCodec.SelectedCamera.CanTilt, + CanZoom = camerasCodec.SelectedCamera.CanZoom, + CanFocus = camerasCodec.SelectedCamera.CanFocus, + }; + } + + if (camerasCodec.ControllingFarEndCameraFeedback != null) + camera.IsFarEnd = camerasCodec.ControllingFarEndCameraFeedback.BoolValue; + + + return camera; + } + + private List GetCurrentPresets() + { + var presetsCodec = Codec as IHasCodecRoomPresets; + + List currentPresets = null; + + if (presetsCodec != null && Codec is IHasFarEndCameraControl && + (Codec as IHasFarEndCameraControl).ControllingFarEndCameraFeedback.BoolValue) + currentPresets = presetsCodec.FarEndRoomPresets; + else if (presetsCodec != null) currentPresets = presetsCodec.NearEndPresets; + + return currentPresets; + } + } + + /// + /// Represents a VideoCodecBaseStateMessage + /// + public class VideoCodecBaseStateMessage : DeviceStateMessageBase + { + + [JsonProperty("calls", NullValueHandling = NullValueHandling.Ignore)] + public List Calls { get; set; } + + [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] + public string CameraMode { get; set; } + + [JsonProperty("cameraSelfView", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraSelfViewIsOn { get; set; } + + [JsonProperty("cameras", NullValueHandling = NullValueHandling.Ignore)] + /// + /// Gets or sets the Cameras + /// + public CameraStatus Cameras { get; set; } + + [JsonProperty("cameraSupportsAutoMode", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraSupportsAutoMode { get; set; } + + [JsonProperty("cameraSupportsOffMode", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraSupportsOffMode { get; set; } + + + /// + /// Gets or sets the CurrentDialString + /// + [JsonProperty("currentDialString", NullValueHandling = NullValueHandling.Ignore)] + public string CurrentDialString { get; set; } + + + /// + /// Gets or sets the CurrentDirectory + /// + [JsonProperty("currentDirectory", NullValueHandling = NullValueHandling.Ignore)] + public CodecDirectory CurrentDirectory { get; set; } + + + /// + /// Gets or sets the DirectorySelectedFolderName + /// + [JsonProperty("directorySelectedFolderName", NullValueHandling = NullValueHandling.Ignore)] + public string DirectorySelectedFolderName { get; set; } + + [JsonProperty("hasCameras", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasCameras { get; set; } + + [JsonProperty("hasDirectory", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasDirectory { get; set; } + + [JsonProperty("hasDirectorySearch", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasDirectorySearch { get; set; } + + [JsonProperty("hasPresets", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasPresets { get; set; } + + [JsonProperty("hasRecents", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasRecents { get; set; } + + [JsonProperty("initialPhonebookSyncComplete", NullValueHandling = NullValueHandling.Ignore)] + public bool? InitialPhonebookSyncComplete { get; set; } + + + /// + /// Gets or sets the Info + /// + [JsonProperty("info", NullValueHandling = NullValueHandling.Ignore)] + public VideoCodecInfo Info { get; set; } + + [JsonProperty("isInCall", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsInCall { get; set; } + + [JsonProperty("isReady", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsReady { get; set; } + + [JsonProperty("isZoomRoom", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsZoomRoom { get; set; } + + + /// + /// Gets or sets the MeetingInfo + /// + [JsonProperty("meetingInfo", NullValueHandling = NullValueHandling.Ignore)] + public MeetingInfo MeetingInfo { get; set; } + + + /// + /// Gets or sets the Presets + /// + [JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)] + public List Presets { get; set; } + + [JsonProperty("privacyModeIsOn", NullValueHandling = NullValueHandling.Ignore)] + public bool? PrivacyModeIsOn { get; set; } + + [JsonProperty("receivingContent", NullValueHandling = NullValueHandling.Ignore)] + public bool? ReceivingContent { get; set; } + + [JsonProperty("recentCalls", NullValueHandling = NullValueHandling.Ignore)] + public List RecentCalls { get; set; } + + [JsonProperty("sharingContentIsOn", NullValueHandling = NullValueHandling.Ignore)] + public bool? SharingContentIsOn { get; set; } + + + /// + /// Gets or sets the SharingSource + /// + [JsonProperty("sharingSource", NullValueHandling = NullValueHandling.Ignore)] + public string SharingSource { get; set; } + + [JsonProperty("showCamerasWhenNotInCall", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowCamerasWhenNotInCall { get; set; } + + [JsonProperty("showSelfViewByDefault", NullValueHandling = NullValueHandling.Ignore)] + public bool? ShowSelfViewByDefault { get; set; } + + [JsonProperty("standbyIsOn", NullValueHandling = NullValueHandling.Ignore)] + public bool? StandbyIsOn { get; set; } + + [JsonProperty("supportsAdHocMeeting", NullValueHandling = NullValueHandling.Ignore)] + public bool? SupportsAdHocMeeting { get; set; } + } + + /// + /// Represents a CameraStatus + /// + public class CameraStatus + { + [JsonProperty("cameraManualSupported", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraManualIsSupported { get; set; } + + [JsonProperty("cameraAutoSupported", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraAutoIsSupported { get; set; } + + [JsonProperty("cameraOffSupported", NullValueHandling = NullValueHandling.Ignore)] + public bool? CameraOffIsSupported { get; set; } + + + /// + /// Gets or sets the CameraMode + /// + [JsonProperty("cameraMode", NullValueHandling = NullValueHandling.Ignore)] + public string CameraMode { get; set; } + + + /// + /// Gets or sets the Cameras + /// + [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] + public List Cameras { get; set; } + + + /// + /// Gets or sets the SelectedCamera + /// + [JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)] + public Camera SelectedCamera { get; set; } + } + + /// + /// Represents a Camera + /// + public class Camera + { + + /// + /// Gets or sets the Key + /// + [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] + public string Key { get; set; } + + + /// + /// Gets or sets the Name + /// + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + + [JsonProperty("isFarEnd", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsFarEnd { get; set; } + + + /// + /// Gets or sets the Capabilities + /// + [JsonProperty("capabilities", NullValueHandling = NullValueHandling.Ignore)] + public CameraCapabilities Capabilities { get; set; } + } + + /// + /// Represents a CameraCapabilities + /// + public class CameraCapabilities + { + [JsonProperty("canPan", NullValueHandling = NullValueHandling.Ignore)] + public bool? CanPan { get; set; } + + [JsonProperty("canTilt", NullValueHandling = NullValueHandling.Ignore)] + public bool? CanTilt { get; set; } + + [JsonProperty("canZoom", NullValueHandling = NullValueHandling.Ignore)] + public bool? CanZoom { get; set; } + + [JsonProperty("canFocus", NullValueHandling = NullValueHandling.Ignore)] + public bool? CanFocus { get; set; } + + } + + /// + /// Represents a VideoCodecBaseEventMessage + /// + public class VideoCodecBaseEventMessage : DeviceEventMessageBase + { + + } + + /// + /// Represents a PasswordPromptEventMessage + /// + public class PasswordPromptEventMessage : VideoCodecBaseEventMessage + { + /// + /// Gets or sets the Message + /// + [JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)] + public string Message { get; set; } + + + /// + /// Gets or sets the LastAttemptWasIncorrect + /// + [JsonProperty("lastAttemptWasIncorrect", NullValueHandling = NullValueHandling.Ignore)] + public bool LastAttemptWasIncorrect { get; set; } + + + /// + /// Gets or sets the LoginAttemptFailed + /// + [JsonProperty("loginAttemptFailed", NullValueHandling = NullValueHandling.Ignore)] + public bool LoginAttemptFailed { get; set; } + + + /// + /// Gets or sets the LoginAttemptCancelled + /// + [JsonProperty("loginAttemptCancelled", NullValueHandling = NullValueHandling.Ignore)] + public bool LoginAttemptCancelled { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlMessage.cs b/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlMessage.cs index abb6de5e..a7a072ba 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlMessage.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlMessage.cs @@ -9,22 +9,22 @@ namespace PepperDash.Essentials.AppServer.Messengers ///
public class MobileControlMessage : IMobileControlMessage { + [JsonProperty("type")] /// /// Gets or sets the Type /// - [JsonProperty("type")] public string Type { get; set; } + [JsonProperty("clientId")] /// /// Gets or sets the ClientId /// - [JsonProperty("clientId")] public string ClientId { get; set; } + [JsonProperty("content")] /// /// Gets or sets the Content /// - [JsonProperty("content")] public JToken Content { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlSimpleContent.cs b/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlSimpleContent.cs index 96d80f87..71972fc3 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlSimpleContent.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/MobileControlSimpleContent.cs @@ -1,18 +1,16 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials.AppServer; - - -/// -/// Represents a MobileControlSimpleContent -/// -public class MobileControlSimpleContent +namespace PepperDash.Essentials.AppServer { /// - /// Gets or sets the Value + /// Represents a MobileControlSimpleContent /// - [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] - - public T Value { get; set; } + public class MobileControlSimpleContent + { + [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] + /// + /// Gets or sets the Value + /// + public T Value { get; set; } + } } - diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj index 0d948cc0..a9223061 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj +++ b/src/PepperDash.Essentials.MobileControl.Messengers/PepperDash.Essentials.MobileControl.Messengers.csproj @@ -1,7 +1,7 @@  PepperDash.Essentials.AppServer - net8 + net472 mobile-control-messengers mobile-control-messengers mobile-control-messengers @@ -33,8 +33,7 @@ - - + diff --git a/src/PepperDash.Essentials.MobileControl.Messengers/SIMPLJoinMaps/SIMPLVtcJoinMap.cs b/src/PepperDash.Essentials.MobileControl.Messengers/SIMPLJoinMaps/SIMPLVtcJoinMap.cs index 67ed8877..b53424bd 100644 --- a/src/PepperDash.Essentials.MobileControl.Messengers/SIMPLJoinMaps/SIMPLVtcJoinMap.cs +++ b/src/PepperDash.Essentials.MobileControl.Messengers/SIMPLJoinMaps/SIMPLVtcJoinMap.cs @@ -1,4 +1,3 @@ - using PepperDash.Essentials.Core; @@ -554,4 +553,4 @@ namespace PepperDash.Essentials.AppServer { } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/AuthorizationResponse.cs b/src/PepperDash.Essentials.MobileControl/AuthorizationResponse.cs index 4636f68a..7ba130bc 100644 --- a/src/PepperDash.Essentials.MobileControl/AuthorizationResponse.cs +++ b/src/PepperDash.Essentials.MobileControl/AuthorizationResponse.cs @@ -1,38 +1,37 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials; - - -/// -/// Represents a AuthorizationResponse -/// -public class AuthorizationResponse +namespace PepperDash.Essentials { + /// + /// Represents a AuthorizationResponse + /// + public class AuthorizationResponse + { + + /// + /// Gets or sets the Authorized + /// + [JsonProperty("authorized")] + public bool Authorized { get; set; } + + + /// + /// Gets or sets the Reason + /// + [JsonProperty("reason", NullValueHandling = NullValueHandling.Ignore)] + public string Reason { get; set; } = null; + } /// - /// Gets or sets the Authorized + /// Represents a AuthorizationRequest /// - [JsonProperty("authorized")] - public bool Authorized { get; set; } + public class AuthorizationRequest + { - - /// - /// Gets or sets the Reason - /// - [JsonProperty("reason", NullValueHandling = NullValueHandling.Ignore)] - public string Reason { get; set; } = null; + /// + /// Gets or sets the GrantCode + /// + [JsonProperty("grantCode")] + public string GrantCode { get; set; } + } } - -/// -/// Represents a AuthorizationRequest -/// -public class AuthorizationRequest -{ - - /// - /// Gets or sets the GrantCode - /// - [JsonProperty("grantCode")] - public string GrantCode { get; set; } -} - diff --git a/src/PepperDash.Essentials.MobileControl/ClientSpecificUpdateRequest.cs b/src/PepperDash.Essentials.MobileControl/ClientSpecificUpdateRequest.cs new file mode 100644 index 00000000..b974a9aa --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/ClientSpecificUpdateRequest.cs @@ -0,0 +1,25 @@ +using System; + +namespace PepperDash.Essentials +{ + /// + /// Send an update request for a specific client + /// + [Obsolete] + public class ClientSpecificUpdateRequest + { + /// + /// Initialize an instance of the class. + /// + /// + public ClientSpecificUpdateRequest(Action action) + { + ResponseMethod = action; + } + + /// + /// Gets or sets the ResponseMethod + /// + public Action ResponseMethod { get; private set; } + } +} diff --git a/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs b/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs new file mode 100644 index 00000000..68f5e36e --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/ConnectedClientVersionInfo.cs @@ -0,0 +1,61 @@ +using System; +using Newtonsoft.Json; + +namespace PepperDash.Essentials +{ + /// + /// Represents the version information reported by a connected Mobile Control UI client + /// + public class ConnectedClientVersionInfo + { + /// + /// Gets or sets the client id + /// + [JsonProperty("clientId")] + public string ClientId { get; set; } + + /// + /// Gets or sets the room key the client joined + /// + [JsonProperty("roomKey")] + public string RoomKey { get; set; } + + /// + /// Gets or sets the touchpanel key the client joined as, if any + /// + [JsonProperty("touchpanelKey")] + public string TouchpanelKey { get; set; } + + /// + /// Gets or sets the app version reported by the client (e.g. the React app's build-time APP_VERSION) + /// + [JsonProperty("appVersion")] + public string AppVersion { get; set; } + + /// + /// Gets or sets the expected app version from the system config's versions.touchpanelWrapperApp, if configured + /// + [JsonProperty("expectedAppVersion")] + public string ExpectedAppVersion { get; set; } + + /// + /// Gets or sets the UTC time the client last reported this version + /// + [JsonProperty("lastSeen")] + public DateTime LastSeen { get; set; } + + /// + /// Returns a copy of this instance, safe for callers outside the owning lock to hold/mutate + /// without affecting the internally tracked instance + /// + public ConnectedClientVersionInfo Clone() => new ConnectedClientVersionInfo + { + ClientId = ClientId, + RoomKey = RoomKey, + TouchpanelKey = TouchpanelKey, + AppVersion = AppVersion, + ExpectedAppVersion = ExpectedAppVersion, + LastSeen = LastSeen + }; + } +} diff --git a/src/PepperDash.Essentials.MobileControl/MessageToClients.cs b/src/PepperDash.Essentials.MobileControl/MessageToClients.cs index 34924cf9..e1aa5eb5 100644 --- a/src/PepperDash.Essentials.MobileControl/MessageToClients.cs +++ b/src/PepperDash.Essentials.MobileControl/MessageToClients.cs @@ -16,46 +16,35 @@ namespace PepperDash.Essentials ///
public class MessageToClients : IQueueMessage { - private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - Converters = { new IsoDateTimeConverter() } - }; - private readonly MobileControlWebsocketServer _server; - private readonly string _serializedMessage; - private readonly string _clientId; + private readonly object msgToSend; /// - /// Message to send to Direct Server Clients. - /// Serialization occurs here in the caller's thread context (parallel) rather than on the queue thread (sequential). + /// Message to send to Direct Server Clients /// /// message object to send /// WebSocket server instance public MessageToClients(object msg, MobileControlWebsocketServer server) { _server = server; - _serializedMessage = JsonConvert.SerializeObject(msg, Formatting.None, SerializerSettings); - _clientId = (msg as MobileControlMessage)?.ClientId; + msgToSend = msg; } /// - /// Message to send to Direct Server Clients. - /// Serialization occurs here in the caller's thread context (parallel) rather than on the queue thread (sequential). + /// Message to send to Direct Server Clients /// /// message object to send /// WebSocket server instance public MessageToClients(DeviceStateMessageBase msg, MobileControlWebsocketServer server) { _server = server; - _serializedMessage = JsonConvert.SerializeObject(msg, Formatting.None, SerializerSettings); - _clientId = null; + msgToSend = msg; } #region Implementation of IQueueMessage /// - /// Dispatch method - only handles WebSocket send since serialization was done at construction time + /// Dispatch method /// public void Dispatch() { @@ -67,18 +56,24 @@ namespace PepperDash.Essentials return; } - if (_clientId != null) - { - _server.LogVerbose("Message TX To client {clientId}: {message}", _clientId, _serializedMessage); + var message = JsonConvert.SerializeObject(msgToSend, Formatting.None, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } }); - _server.SendMessageToClient(_clientId, _serializedMessage); + var clientSpecificMessage = msgToSend as MobileControlMessage; + if (clientSpecificMessage.ClientId != null) + { + var clientId = clientSpecificMessage.ClientId; + + _server.LogVerbose("Message TX To client {clientId}: {message}", clientId, message); + + _server.SendMessageToClient(clientId, message); return; } - _server.SendMessageToAllClients(_serializedMessage); + _server.SendMessageToAllClients(message); - _server.LogVerbose("Message TX To all clients: {message}", _serializedMessage); + _server.LogVerbose("Message TX To all clients: {message}", message); } catch (ThreadAbortException) { diff --git a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs b/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs deleted file mode 100644 index 29ede86d..00000000 --- a/src/PepperDash.Essentials.MobileControl/MessengerFactoryRegistry.cs +++ /dev/null @@ -1,457 +0,0 @@ -using System; -using System.Collections.Generic; -using PepperDash.Essentials.AppServer.Messengers; -using PepperDash.Essentials.Core; -using PepperDash.Essentials.Core.CrestronIO; -using PepperDash.Essentials.Core.DeviceInfo; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; -using PepperDash.Essentials.Core.Lighting; -using PepperDash.Essentials.Core.Routing; -using PepperDash.Essentials.Core.Shades; -using PepperDash.Essentials.Devices.Common.AudioCodec; -using PepperDash.Essentials.Devices.Common.Cameras; -using PepperDash.Essentials.Devices.Common.Codec; -using PepperDash.Essentials.Devices.Common.VideoCodec; -using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces; -using PepperDash.Essentials.Room.MobileControl; -using PepperDash.Essentials.RoomBridges; - -namespace PepperDash.Essentials -{ - /// - /// Describes a single entry in . - /// - internal sealed class MessengerFactoryEntry - { - /// - /// The primary type used for both IsAssignableFrom matching and for - /// caller-supplied type filtering via . - /// - public Type InterfaceType { get; } - - /// - /// Optional additional condition. When null the entry matches any device - /// whose runtime type satisfies . - /// - public Func Predicate { get; } - - /// - /// Factory that creates the messenger. Parameters are - /// (device, messagePath, controllerKey). - /// - public Func Factory { get; } - - public MessengerFactoryEntry( - Type interfaceType, - Func factory, - Func predicate = null) - { - InterfaceType = interfaceType; - Factory = factory; - Predicate = predicate; - } - - /// - /// Returns true when this entry should produce a messenger for - /// . - /// - public bool Matches(EssentialsDevice device) - { - return Predicate != null - ? Predicate(device) - : InterfaceType.IsAssignableFrom(device.GetType()); - } - } - - /// - /// Maps device interface / class types to messenger factories, replacing the - /// previous cascade of if (device is X) checks in - /// SetupDefaultDeviceMessengers. - /// - internal static class MessengerFactoryRegistry - { - public static IReadOnlyList Entries { get; } = - new List - { - // ── Communication ──────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ICommunicationMonitor), - (d, mp, ck) => new ICommunicationMonitorMessenger( - $"{d.Key}-commMonitor-{ck}", mp, (ICommunicationMonitor)d) - ), - - // ── Cameras ────────────────────────────────────────────────────────────── - // CameraBase only when the device does NOT also implement IHasCameraControls - new MessengerFactoryEntry( - typeof(CameraBase), - (d, mp, ck) => new CameraControlMessenger( - $"{d.Key}-cameraBase-{ck}", (CameraBase)d, mp), - predicate: d => d is CameraBase && !(d is IHasCameraControls) - ), - - new MessengerFactoryEntry( - typeof(IHasCameraControls), - (d, mp, ck) => new CameraControlMessenger( - $"{d.Key}-hasCamerasWithControls-{ck}", (IHasCameraControls)d, mp) - ), - new MessengerFactoryEntry( - typeof(IHasCamerasWithControls), - (d, mp, ck) => new IHasCamerasWithControlsMessenger( - $"{d.Key}-cameras-{ck}", mp, (IHasCamerasWithControls)d) - ), - - // ── Routing ────────────────────────────────────────────────────────────── - // BlueJeansPc implements IRunRouteAction - new MessengerFactoryEntry( - typeof(IRunRouteAction), - (d, mp, ck) => new RunRouteActionMessenger( - $"{d.Key}-runRouteAction-{ck}", (IRunRouteAction)d, mp) - ), - - // ── Presets ────────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ITvPresetsProvider), - (d, mp, ck) => new ITvPresetsProviderMessenger( - $"{d.Key}-presets-{ck}", mp, (ITvPresetsProvider)d) - ), - - // ── Displays ───────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IRoutingSinkWithFeedback), - (d, mp, ck) => new IRoutingSinkWithFeedbackMessenger( - $"{d.Key}-displayBase-{ck}", mp, (IRoutingSinkWithFeedback)d) - ), - new MessengerFactoryEntry( - typeof(IDisplayCurrentInput), - (d, mp, ck) => new IDisplayCurrentInputMessenger( - $"{d.Key}-twoWayDisplay-{ck}", mp, (IDisplayCurrentInput)d) - ), - new MessengerFactoryEntry( - typeof(IWarmingCooling), - (d, mp, ck) => new IWarmingCoolingMessenger( - $"{d.Key}-warmingCooling-{ck}", mp, d) - ), - - // ── Audio / Video ───────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IBasicVolumeControls), - (d, mp, ck) => new IBasicVolumeControlsMessenger( - $"{d.Key}-volume-{ck}", mp, (IBasicVolumeControls)d) - ), - new MessengerFactoryEntry( - typeof(IBasicVideoMuteWithFeedback), - (d, mp, ck) => new IBasicVideoMuteWithFeedbackMessenger( - $"{d.Key}-videoMute-{ck}", mp, (IBasicVideoMuteWithFeedback)d) - ), - - // ── Lighting ───────────────────────────────────────────────────────────── - // ILightingScenes covers LightingBase too (LightingBase : ILightingScenes) - new MessengerFactoryEntry( - typeof(ILightingScenes), - (d, mp, ck) => new ILightingScenesMessenger( - $"{d.Key}-lighting-{ck}", (ILightingScenes)d, mp) - ), - - // ── Shades ─────────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IShadesOpenCloseStop), - (d, mp, ck) => new IShadesOpenCloseStopMessenger( - $"{d.Key}-shades-{ck}", (IShadesOpenCloseStop)d, mp) - ), - - // ── Codecs ─────────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IHasReady), - (d, mp, ck) => new IHasReadyMessenger( - $"{d.Key}-ready-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasDialer), - (d, mp, ck) => new IHasDialerMessenger( - $"{d.Key}-dialer-{ck}", mp, d), - // IDialerCallStatus and ICodecCallControls both extend IHasDialer and each already - // provide their own richer messenger (posting a "calls" array) subscribed to the same - // CallStatusChange event and the same "/device/{key}" message path. Without this - // exclusion, a device implementing either interface would get both messengers posting - // conflicting state shapes ("callItem" vs "calls") to the same clients. - predicate: d => d is IHasDialer && !(d is IDialerCallStatus) && !(d is ICodecCallControls) - ), - new MessengerFactoryEntry( - typeof(ICodecCallControls), - (d, mp, ck) => new ICodecCallControlsMessenger( - $"{d.Key}-callControls-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasScheduleAwareness), - (d, mp, ck) => new IHasScheduleAwarenessMessenger( - $"{d.Key}-schedule-{ck}", (IHasScheduleAwareness)d, mp) - ), - new MessengerFactoryEntry( - typeof(IHasContentSharing), - (d, mp, ck) => new IHasContentSharingMessenger( - $"{d.Key}-contentSharing-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasStandbyMode), - (d, mp, ck) => new IHasStandbyModeMessenger( - $"{d.Key}-standby-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IPrivacy), - (d, mp, ck) => new IPrivacyMessenger( - $"{d.Key}-privacy-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IVideoCodecInfo), - (d, mp, ck) => new IVideoCodecInfoMessenger( - $"{d.Key}-codecInfo-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasDirectory), - (d, mp, ck) => new IHasDirectoryMessenger( - $"{d.Key}-directory-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasCallHistory), - (d, mp, ck) => new IHasCallHistoryMessenger( - $"{d.Key}-callHistory-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IPasswordPrompt), - (d, mp, ck) => new IPasswordPromptMessenger( - $"{d.Key}-passwordPrompt-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasCodecCameras), - (d, mp, ck) => new IHasCodecCamerasMessenger( - $"{d.Key}-codecCameras-{ck}", mp, (VideoCodecBase)d), - predicate: d => d is VideoCodecBase && d is IHasCodecCameras - ), - new MessengerFactoryEntry( - typeof(IHasCodecRoomPresets), - (d, mp, ck) => new IHasCodecRoomPresetsMessenger( - $"{d.Key}-codecRoomPresets-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasCodecSelfView), - (d, mp, ck) => new IHasCodecSelfViewMessenger( - $"{d.Key}-selfView-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasCodecLayouts), - (d, mp, ck) => new IHasCodecLayoutsMessenger( - $"{d.Key}-codecLayouts-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasFarEndContentStatus), - (d, mp, ck) => new IHasFarEndContentStatusMessenger( - $"{d.Key}-farEndContent-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasParticipantAudioMute), - (d, mp, ck) => new IHasParticipantAudioMuteMessenger( - $"{d.Key}-participantAudioMute-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasParticipantVideoMute), - (d, mp, ck) => new IHasParticipantVideoMuteMessenger( - $"{d.Key}-participantVideoMute-{ck}", mp, d), - predicate: d => d is IHasParticipantVideoMute && !(d is IHasParticipantAudioMute) - ), - new MessengerFactoryEntry( - typeof(IHasMeetingInfo), - (d, mp, ck) => new IHasMeetingInfoMessenger( - $"{d.Key}-meetingInfo-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IHasStartMeeting), - (d, mp, ck) => new IHasStartMeetingMessenger( - $"{d.Key}-startMeeting-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IDialerCallStatus), - (d, mp, ck) => new IDialerCallStatusMessenger( - $"{d.Key}-audioCodec-{ck}", (IDialerCallStatus)d, mp) - ), - new MessengerFactoryEntry( - typeof(IAudioCodecInfo), - (d, mp, ck) => new IAudioCodecInfoMessenger( - $"{d.Key}-audioCodecInfo-{ck}", mp, d) - ), - new MessengerFactoryEntry( - typeof(IAudioCodecPhonebook), - (d, mp, ck) => new IAudioCodecPhonebookMessenger( - $"{d.Key}-audioCodecPhonebook-{ck}", mp, d) - ), - - // ── Set-top box controls ────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ISetTopBoxControls), - (d, mp, ck) => new ISetTopBoxControlsMessenger( - $"{d.Key}-stb-{ck}", mp, (ISetTopBoxControls)d) - ), - new MessengerFactoryEntry( - typeof(IChannel), - (d, mp, ck) => new IChannelMessenger( - $"{d.Key}-channel-{ck}", mp, (IChannel)d) - ), - new MessengerFactoryEntry( - typeof(IColor), - (d, mp, ck) => new IColorMessenger( - $"{d.Key}-color-{ck}", mp, (IColor)d) - ), - new MessengerFactoryEntry( - typeof(IDPad), - (d, mp, ck) => new IDPadMessenger( - $"{d.Key}-dPad-{ck}", mp, (IDPad)d) - ), - new MessengerFactoryEntry( - typeof(INumericKeypad), - (d, mp, ck) => new INumericKeypadMessenger( - $"{d.Key}-numericKeypad-{ck}", mp, (INumericKeypad)d) - ), - - // ── Power ───────────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IHasPowerControl), - (d, mp, ck) => new IHasPowerMessenger( - $"{d.Key}-powerControl-{ck}", mp, (IHasPowerControl)d) - ), - new MessengerFactoryEntry( - typeof(IHasPowerControlWithFeedback), - (d, mp, ck) => new IHasPowerControlWithFeedbackMessenger( - $"{d.Key}-powerFeedback-{ck}", mp, (IHasPowerControlWithFeedback)d) - ), - - // ── Transport / sources ─────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ITransport), - (d, mp, ck) => new ITransportMessenger( - $"{d.Key}-transport-{ck}", mp, (ITransport)d) - ), - new MessengerFactoryEntry( - typeof(ICurrentSources), - (d, mp, ck) => new ICurrentSourcesMessenger( - $"{d.Key}-currentSources-{ck}", mp, (ICurrentSources)d) - ), - new MessengerFactoryEntry( - typeof(ISwitchedOutput), - (d, mp, ck) => new ISwitchedOutputMessenger( - $"{d.Key}-switchedOutput-{ck}", (ISwitchedOutput)d, mp) - ), - - // ── Device info / levels / inputs ───────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IDeviceInfoProvider), - (d, mp, ck) => new IDeviceInfoProviderMessenger( - $"{d.Key}-deviceInfo-{ck}", mp, (IDeviceInfoProvider)d) - ), - new MessengerFactoryEntry( - typeof(ILevelControls), - (d, mp, ck) => new ILevelControlsMessenger( - $"{d.Key}-levelControls-{ck}", mp, (ILevelControls)d) - ), - new MessengerFactoryEntry( - typeof(IHasInputs), - (d, mp, ck) => new IHasInputsMessenger( - $"{d.Key}-inputs-{ck}", mp, (IHasInputs)d) - ), - new MessengerFactoryEntry( - typeof(IHasInputs), - (d, mp, ck) => new IHasInputsMessenger( - $"{d.Key}-inputs-{ck}", mp, (IHasInputs)d) - ), - new MessengerFactoryEntry( - typeof(IHasInputs), - (d, mp, ck) => new IHasInputsMessenger( - $"{d.Key}-inputs-{ck}", mp, (IHasInputs)d) - ), - - // ── Matrix routing ──────────────────────────────────────────────────────── - // Preserving original key format (no controller key suffix) - new MessengerFactoryEntry( - typeof(IRoutingMidpointWithFeedback), - (d, mp, _) => new IRoutingMidpointWithFeedbackMessenger( - $"{d.Key}-matrixRouting", mp, (IRoutingMidpointWithFeedback)d) - ), - // Devices that also expose named slots (plugin-local IDmInputSlot/INvxInputSlot- - // style abstractions) get a second, richer message with names + per-signal-type - // route feedback the bare messenger above cannot provide. - new MessengerFactoryEntry( - typeof(IHasNamedRoutingSlots), - (d, mp, _) => new IHasNamedRoutingSlotsMessenger( - $"{d.Key}-namedRoutingSlots", mp, (IHasNamedRoutingSlots)d) - ), - - // ── Environmental sensors ───────────────────────────────────────────────── - // Preserving original key format (no controller key suffix) - new MessengerFactoryEntry( - typeof(ITemperatureSensor), - (d, mp, _) => new ITemperatureSensorMessenger( - $"{d.Key}-tempSensor", (ITemperatureSensor)d, mp) - ), - new MessengerFactoryEntry( - typeof(IHumiditySensor), - (d, mp, _) => new IHumiditySensorMessenger( - $"{d.Key}-humiditySensor", (IHumiditySensor)d, mp) - ), - - // ── Room combining ──────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IEssentialsRoomCombiner), - (d, mp, ck) => new IEssentialsRoomCombinerMessenger( - $"{d.Key}-roomCombiner-{ck}", mp, (IEssentialsRoomCombiner)d) - ), - - // ── Projector screen ────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IProjectorScreenLiftControl), - (d, mp, ck) => new IProjectorScreenLiftControlMessenger( - $"{d.Key}-screenLiftControl-{ck}", mp, (IProjectorScreenLiftControl)d) - ), - - // ── DSP ─────────────────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IDspPresets), - (d, mp, ck) => new IDspPresetsMessenger( - $"{d.Key}-dspPresets-{ck}", mp, (IDspPresets)d) - ), - - // Room Entries - // ── Event schedule ──────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IRoomEventSchedule), - (d, mp, ck) => new IRoomEventScheduleMessenger( - $"{d.Key}-schedule-{ck}", mp, (IRoomEventSchedule)d) - ), - - // ── Tech password ───────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ITechPassword), - (d, mp, ck) => new ITechPasswordMessenger( - $"{d.Key}-techPassword-{ck}", mp, (ITechPassword)d) - ), - - // ── Shutdown prompt timer ───────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IShutdownPromptTimer), - (d, mp, ck) => new IShutdownPromptTimerMessenger( - $"{d.Key}-shutdownPromptTimer-{ck}", mp, (IShutdownPromptTimer)d) - ), - - // ── Level controls ──────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(ILevelControls), - (d, mp, ck) => new ILevelControlsMessenger( - $"{d.Key}-levelControls-{ck}", mp, (ILevelControls)d) - ), - - // ── Essentials Room ───────────────────────────────────────────────────────── - new MessengerFactoryEntry( - typeof(IEssentialsRoom), - (d, mp, ck) => new MobileControlEssentialsRoomBridge( - (IEssentialsRoom)d) - ), - }; - } -} diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlAction.cs b/src/PepperDash.Essentials.MobileControl/MobileControlAction.cs index 1136c3c1..16a9bd40 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlAction.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlAction.cs @@ -2,33 +2,32 @@ using Newtonsoft.Json.Linq; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -namespace PepperDash.Essentials; - - -/// -/// Represents a MobileControlAction -/// -public class MobileControlAction : IMobileControlAction +namespace PepperDash.Essentials { /// - /// Gets the Messenger + /// Represents a MobileControlAction /// - public IMobileControlMessenger Messenger { get; private set; } - - /// - /// Action to execute when this path is matched - /// - public Action Action { get; private set; } - - /// - /// Initialize an instance of the class - /// - /// Messenger associated with this action - /// Action to take when this path is matched - public MobileControlAction(IMobileControlMessenger messenger, Action handler) + public class MobileControlAction : IMobileControlAction { - Messenger = messenger; - Action = handler; + /// + /// Gets the Messenger + /// + public IMobileControlMessenger Messenger { get; private set; } + + /// + /// Action to execute when this path is matched + /// + public Action Action { get; private set; } + + /// + /// Initialize an instance of the class + /// + /// Messenger associated with this action + /// Action to take when this path is matched + public MobileControlAction(IMobileControlMessenger messenger, Action handler) + { + Messenger = messenger; + Action = handler; + } } } - diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs b/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs index 05355e25..963e7fd5 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlConfig.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -38,6 +39,13 @@ namespace PepperDash.Essentials ///
[JsonProperty("enableApiServer")] public bool EnableApiServer { get; set; } = true; + + /// + /// Enables subscriptions for messengers + /// + [JsonProperty("enableMessengerSubscriptions")] + [Obsolete("This property is obsolete and will be removed in a future version. All messengers are now subscription based.")] + public bool EnableMessengerSubscriptions { get; set; } } /// @@ -282,4 +290,4 @@ namespace PepperDash.Essentials /// NEO } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.schema.json b/src/PepperDash.Essentials.MobileControl/MobileControlConfig.schema.json deleted file mode 100644 index f00c2823..00000000 --- a/src/PepperDash.Essentials.MobileControl/MobileControlConfig.schema.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://pepperdash.com/schemas/Essentials/MobileControlConfig.schema.json", - "title": "appServer", - "description": "Device config entry for a device of type \"appServer\" (also \"mobilecontrol\", \"webserver\") - the Mobile Control app server / websocket gateway - backed by MobileControlConfig (MobileControlConfig.cs).", - "type": "object", - "properties": { - "key": { - "type": "string", - "description": "Unique identifier for this device within the configuration file." - }, - "name": { - "type": "string", - "description": "Friendly display name for this device." - }, - "type": { - "type": "string", - "enum": ["appServer", "appserver", "mobilecontrol", "webserver"], - "description": "Discriminator identifying the plugin factory that instantiates this device. Accepted aliases are equivalent." - }, - "properties": { - "$ref": "#/$defs/properties", - "description": "Properties object for a device of type \"appServer\", backed by MobileControlConfig (MobileControlConfig.cs)." - } - }, - "required": ["key", "name", "type", "properties"], - "$defs": { - "properties": { - "type": "object", - "properties": { - "serverUrl": { - "type": "string", - "description": "URL of the Mobile Control cloud server, if using cloud-hosted mode." - }, - "clientAppUrl": { - "type": "string", - "description": "URL override for the client app, if not using the default." - }, - "directServer": { "$ref": "#/$defs/directServer" }, - "applicationConfig": { "$ref": "#/$defs/applicationConfig" }, - "enableApiServer": { - "type": "boolean", - "default": true, - "description": "Enables the REST API server." - } - } - }, - "directServer": { - "type": "object", - "description": "Configuration for the local (on-processor) direct websocket/HTTP server (MobileControlDirectServerPropertiesConfig).", - "properties": { - "enableDirectServer": { - "type": "boolean", - "description": "Enables the local direct server (bypassing the cloud)." - }, - "port": { - "type": "integer", - "description": "TCP port the direct server listens on." - }, - "logging": { "$ref": "#/$defs/loggingConfig" }, - "automaticallyForwardPortToCSLAN": { - "type": ["boolean", "null"], - "description": "Automatically forward the direct server's port to the Crestron CS LAN interface." - }, - "csLanUiDeviceKeys": { - "type": "array", - "items": { "type": "string" }, - "description": "Device keys that should receive the CS LAN IP address instead of the LAN IP address." - }, - "Secure": { - "type": "boolean", - "description": "Indicates whether the direct server connection is secure (HTTPS)." - } - } - }, - "loggingConfig": { - "type": "object", - "description": "Remote logging configuration for the direct server (MobileControlLoggingConfig).", - "properties": { - "enableRemoteLogging": { "type": "boolean" }, - "host": { "type": "string" }, - "port": { "type": "integer" } - } - }, - "applicationConfig": { - "type": "object", - "description": "Client application configuration for Mobile Control (MobileControlApplicationConfig).", - "properties": { - "apiPath": { "type": "string" }, - "gatewayAppPath": { "type": "string" }, - "enableDev": { "type": ["boolean", "null"] }, - "logoPath": { "type": "string" }, - "iconSet": { - "type": "string", - "pattern": "^(?:[gG][oO][oO][gG][lL][eE]|[hH][aA][bB][aA][nN][eE][rR][oO]|[nN][eE][oO])$", - "description": "Icon set used by the client app. Case-insensitive." - }, - "loginMode": { "type": "string" }, - "modes": { - "type": "object", - "additionalProperties": { "$ref": "#/$defs/mcMode" } - }, - "enableRemoteLogging": { - "type": "boolean", - "description": "Note: serialized as \"enableRemoteLogging\" but backed by the ApplicationConfig.Logging property (distinct from directServer.logging)." - }, - "partnerMetadata": { - "type": "array", - "items": { "$ref": "#/$defs/partnerMetadata" } - } - } - }, - "mcMode": { - "type": "object", - "properties": { - "listPageText": { "type": "string" }, - "loginHelpText": { "type": "string" }, - "passcodePageText": { "type": "string" } - } - }, - "partnerMetadata": { - "type": "object", - "properties": { - "role": { "type": "string" }, - "description": { "type": "string" }, - "logoPath": { "type": "string" } - } - } - } -} diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlDeviceFactory.cs b/src/PepperDash.Essentials.MobileControl/MobileControlDeviceFactory.cs index c08b9c9f..6147b14e 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlDeviceFactory.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlDeviceFactory.cs @@ -5,26 +5,34 @@ using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials; - -public class MobileControlDeviceFactory : EssentialsDeviceFactory +namespace PepperDash.Essentials { - public MobileControlDeviceFactory() + /// + /// Factory to create a Mobile Control System Controller + /// + public class MobileControlDeviceFactory : EssentialsDeviceFactory { - TypeNames = new List { "appserver", "mobilecontrol", "webserver" }; - } - - public override EssentialsDevice BuildDevice(DeviceConfig dc) - { - try + /// + /// Create the factory for a Mobile Control System Controller + /// + public MobileControlDeviceFactory() { - var props = dc.Properties.ToObject(); - return new MobileControlSystemController(dc.Key, dc.Name, props); + TypeNames = new List { "appserver", "mobilecontrol", "webserver" }; } - catch (Exception e) + + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) { - Debug.LogMessage(e, "Error building Mobile Control System Controller"); - return null; + try + { + var props = dc.Properties.ToObject(); + return new MobileControlSystemController(dc.Key, dc.Name, props); + } + catch (Exception e) + { + Debug.LogMessage(e, "Error building Mobile Control System Controller"); + return null; + } } } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlEssentialsConfig.cs b/src/PepperDash.Essentials.MobileControl/MobileControlEssentialsConfig.cs index a0b5ebb8..d0415287 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlEssentialsConfig.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlEssentialsConfig.cs @@ -1,75 +1,74 @@ using System.Collections.Generic; using Newtonsoft.Json; -using PepperDash.Essentials; using PepperDash.Essentials.Core.Config; -namespace PepperDash.Essentials; - -/// -/// Configuration class for sending data to Mobile Control Edge or a client using the Direct Server -/// -public class MobileControlEssentialsConfig : EssentialsConfig +namespace PepperDash.Essentials { /// - /// Current versions for the system + /// Configuration class for sending data to Mobile Control Edge or a client using the Direct Server /// - [JsonProperty("runtimeInfo")] - public MobileControlRuntimeInfo RuntimeInfo { get; set; } - - /// - /// Create Configuration for Mobile Control. Used as part of the data sent to a client - /// - /// The base configuration - public MobileControlEssentialsConfig(EssentialsConfig config) - : base() + public class MobileControlEssentialsConfig : EssentialsConfig { - Devices = config.Devices; - Info = config.Info; - JoinMaps = config.JoinMaps; - Rooms = config.Rooms; - SourceLists = config.SourceLists; - DestinationLists = config.DestinationLists; - SystemUrl = config.SystemUrl; - TemplateUrl = config.TemplateUrl; - TieLines = config.TieLines; + /// + /// Current versions for the system + /// + [JsonProperty("runtimeInfo")] + public MobileControlRuntimeInfo RuntimeInfo { get; set; } - if (Info == null) - Info = new InfoConfig(); + /// + /// Create Configuration for Mobile Control. Used as part of the data sent to a client + /// + /// The base configuration + public MobileControlEssentialsConfig(EssentialsConfig config) + : base() + { + Devices = config.Devices; + Info = config.Info; + JoinMaps = config.JoinMaps; + Rooms = config.Rooms; + SourceLists = config.SourceLists; + DestinationLists = config.DestinationLists; + SystemUrl = config.SystemUrl; + TemplateUrl = config.TemplateUrl; + TieLines = config.TieLines; - RuntimeInfo = new MobileControlRuntimeInfo(); + if (Info == null) + Info = new InfoConfig(); + + RuntimeInfo = new MobileControlRuntimeInfo(); + } } -} - -/// -/// Represents a MobileControlRuntimeInfo -/// -public class MobileControlRuntimeInfo -{ - /// - /// Gets or sets the PluginVersion + /// Represents a MobileControlRuntimeInfo /// - [JsonProperty("pluginVersion")] - public string PluginVersion { get; set; } + public class MobileControlRuntimeInfo + { - /// - /// Essentials Version - /// - [JsonProperty("essentialsVersion")] - public string EssentialsVersion { get; set; } + /// + /// Gets or sets the PluginVersion + /// + [JsonProperty("pluginVersion")] + public string PluginVersion { get; set; } - /// - /// PepperDash Core Version - /// - [JsonProperty("pepperDashCoreVersion")] - public string PepperDashCoreVersion { get; set; } + /// + /// Essentials Version + /// + [JsonProperty("essentialsVersion")] + public string EssentialsVersion { get; set; } + + /// + /// PepperDash Core Version + /// + [JsonProperty("pepperDashCoreVersion")] + public string PepperDashCoreVersion { get; set; } - /// - /// List of Plugins loaded on this system - /// - [JsonProperty("essentialsPlugins")] - public List EssentialsPlugins { get; set; } -} + /// + /// List of Plugins loaded on this system + /// + [JsonProperty("essentialsPlugins")] + public List EssentialsPlugins { get; set; } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs b/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs index cba0afd5..4f3c5433 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlFactory.cs @@ -4,54 +4,61 @@ using System.Reflection; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials; - -public class MobileControlFactory +namespace PepperDash.Essentials { - public MobileControlFactory() - { - var assembly = Assembly.GetExecutingAssembly(); - - PluginLoader.AddLoadedAssembly(assembly.GetName().Name, assembly); - - var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); - - if (types == null) - { - return; - } - - foreach (var type in types) - { - try - { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - - LoadDeviceFactories(factory); - } - catch (Exception ex) - { - Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name); - } - } - } - /// - /// Loads device factories from the specified plugin device factory and registers them for use. + /// Factory class for the Mobile Control App Controller /// - /// This method retrieves metadata from the provided , including - /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type - /// names are converted to lowercase for registration. - /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. - private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + public class MobileControlFactory { - foreach (var typeName in deviceFactory.TypeNames) + /// + /// Create an instance of the class. + /// + public MobileControlFactory() { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + var assembly = Assembly.GetExecutingAssembly(); + + PluginLoader.SetEssentialsAssembly(assembly.GetName().Name, assembly); + + var types = assembly.GetTypes().Where(t => typeof(IDeviceFactory).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + if (types == null) + { + return; + } + + foreach (var type in types) + { + try + { + var factory = (IDeviceFactory)Activator.CreateInstance(type); + + LoadDeviceFactories(factory); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Unable to load type '{type}' DeviceFactory: {factory}", null, type.Name); + } + } + } + + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) + { + foreach (var typeName in deviceFactory.TypeNames) + { + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; // Default value if no DescriptionAttribute is found + + DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } } } } diff --git a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs index c2c080fc..c17cf3b9 100644 --- a/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs +++ b/src/PepperDash.Essentials.MobileControl/MobileControlSystemController.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Timers; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.Net.Http; @@ -17,10 +16,22 @@ using PepperDash.Essentials.AppServer; using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; +using PepperDash.Essentials.Core.CrestronIO; +using PepperDash.Essentials.Core.DeviceInfo; using PepperDash.Essentials.Core.DeviceTypeInterfaces; +using PepperDash.Essentials.Core.Lighting; using PepperDash.Essentials.Core.Monitoring; using PepperDash.Essentials.Core.Queues; +using PepperDash.Essentials.Core.Routing; +using PepperDash.Essentials.Core.Shades; using PepperDash.Essentials.Core.Web; +using PepperDash.Essentials.Devices.Common.AudioCodec; +using PepperDash.Essentials.Devices.Common.Cameras; +using PepperDash.Essentials.Devices.Common.Displays; +using PepperDash.Essentials.Devices.Common.Lighting; +using PepperDash.Essentials.Devices.Common.SoftCodec; +using PepperDash.Essentials.Devices.Common.VideoCodec; +using PepperDash.Essentials.Room.MobileControl; using PepperDash.Essentials.RoomBridges; using PepperDash.Essentials.Services; using PepperDash.Essentials.WebApiHandlers; @@ -58,15 +69,36 @@ namespace PepperDash.Essentials private readonly Dictionary _defaultMessengers = new Dictionary(); + private readonly Dictionary _connectedClientVersions = + new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + private readonly object _connectedClientVersionsLock = new object(); + /// - /// Get the custom messengers + /// Get the custom messengers with subscriptions /// - public ReadOnlyDictionary Messengers => new ReadOnlyDictionary(_messengers); + public ReadOnlyDictionary Messengers => new ReadOnlyDictionary(_messengers.Values.OfType().ToDictionary(k => k.Key, v => v)); + + /// + /// Gets the most recently reported UI app version for each connected client, keyed by clientId + /// + public ReadOnlyDictionary ConnectedClientVersions + { + get + { + lock (_connectedClientVersionsLock) + { + return new ReadOnlyDictionary( + _connectedClientVersions.ToDictionary(kv => kv.Key, kv => kv.Value.Clone()) + ); + } + } + } /// /// Get the default messengers /// - public ReadOnlyDictionary DefaultMessengers => new ReadOnlyDictionary(_defaultMessengers); + public ReadOnlyDictionary DefaultMessengers => new ReadOnlyDictionary(_defaultMessengers.Values.OfType().ToDictionary(k => k.Key, v => v)); private readonly GenericQueue _transmitToServerQueue; @@ -92,7 +124,7 @@ namespace PepperDash.Essentials ///
public MobileControlWebsocketServer DirectServer => _directServer; - private readonly object _wsCriticalSection = new(); + private readonly CCriticalSection _wsCriticalSection = new CCriticalSection(); /// /// Gets or sets the SystemUrl @@ -175,9 +207,9 @@ namespace PepperDash.Essentials /// public DateTime LastAckMessage => _lastAckMessage; - private Timer _pingTimer; + private CTimer _pingTimer; - private Timer _serverReconnectTimer; + private CTimer _serverReconnectTimer; private LogLevel _wsLogLevel = LogLevel.Error; /// @@ -195,15 +227,15 @@ namespace PepperDash.Essentials //_receiveQueue = new ReceiveQueue(key, ParseStreamRx); _receiveQueue = new GenericQueue( key + "-rxqueue", - System.Threading.ThreadPriority.Highest, - 100 + Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, + 25 ); // The queue that will collect the outgoing messages in the order they are received _transmitToServerQueue = new GenericQueue( key + "-txqueue", - System.Threading.ThreadPriority.Highest, - 100 + Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, + 25 ); if (Config.DirectServer != null && Config.DirectServer.EnableDirectServer) @@ -217,8 +249,8 @@ namespace PepperDash.Essentials _transmitToClientsQueue = new GenericQueue( key + "-clienttxqueue", - System.Threading.ThreadPriority.Highest, - 100 + Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority, + 25 ); } @@ -242,11 +274,9 @@ namespace PepperDash.Essentials AddPreActivationAction(() => LinkSystemMonitorToAppServer()); - AddPreActivationAction(() => - { - var actionMessenger = new DeviceActionMessenger("deviceActionMessenger-" + Key, "/action"); - AddDeviceMessenger(actionMessenger); - }); + AddPreActivationAction(() => SetupDefaultDeviceMessengers()); + + AddPreActivationAction(() => SetupDefaultRoomMessengers()); AddPreActivationAction(() => AddWebApiPaths()); @@ -272,39 +302,793 @@ namespace PepperDash.Essentials }); } - /// - public void AddDefaultMessengersForDevice(EssentialsDevice device, IEnumerable interfaces = null) + private void SetupDefaultRoomMessengers() { - var interfaceSet = interfaces != null ? new HashSet(interfaces) : [.. device.GetType().GetInterfaces()]; - var messengerAdded = false; + this.LogVerbose("Setting up room messengers"); - foreach (var entry in MessengerFactoryRegistry.Entries) + foreach (var room in DeviceManager.AllDevices.OfType()) { - if (interfaceSet != null && !interfaceSet.Contains(entry.InterfaceType)) - continue; + this.LogVerbose( + "Setting up room messengers for room: {key}", + room.Key + ); - if (!entry.Matches(device)) - continue; + var messenger = new MobileControlEssentialsRoomBridge(room); - // Room-scoped messengers must share the room bridge's "/room/{key}" path so client requests route correctly - var basePath = device is IEssentialsRoom ? $"/room/{device.Key}" : $"/device/{device.Key}"; + messenger.AddParent(this); - var messenger = entry.Factory(device, basePath, Key); - - if (messenger == null) - continue; + _roomBridges.Add(messenger); AddDefaultDeviceMessenger(messenger); - messengerAdded = true; - } - if (!messengerAdded) + this.LogVerbose( + "Attempting to set up default room messengers for room: {0}", + room.Key + ); + + if (room is IRoomEventSchedule) + { + this.LogInformation("Setting up event schedule messenger for room: {key}", room.Key); + + var scheduleMessenger = new RoomEventScheduleMessenger( + $"{room.Key}-schedule-{Key}", + string.Format("/room/{0}", room.Key), + room as IRoomEventSchedule + ); + + AddDefaultDeviceMessenger(scheduleMessenger); + } + + if (room is ITechPassword) + { + this.LogInformation("Setting up tech password messenger for room: {key}", room.Key); + + var techPasswordMessenger = new ITechPasswordMessenger( + $"{room.Key}-techPassword-{Key}", + string.Format("/room/{0}", room.Key), + room as ITechPassword + ); + + AddDefaultDeviceMessenger(techPasswordMessenger); + } + + if (room is IShutdownPromptTimer) + { + this.LogInformation("Setting up shutdown prompt timer messenger for room: {key}", this, room.Key); + + var shutdownPromptTimerMessenger = new IShutdownPromptTimerMessenger( + $"{room.Key}-shutdownPromptTimer-{Key}", + string.Format("/room/{0}", room.Key), + room as IShutdownPromptTimer + ); + + AddDefaultDeviceMessenger(shutdownPromptTimerMessenger); + } + + if (room is ILevelControls levelControls) + { + this.LogInformation("Setting up level controls messenger for room: {key}", this, room.Key); + + var levelControlsMessenger = new ILevelControlsMessenger( + $"{room.Key}-levelControls-{Key}", + $"/room/{room.Key}", + levelControls + ); + + AddDefaultDeviceMessenger(levelControlsMessenger); + } + } + } + + /// + /// Set up the messengers for each device type + /// + private void SetupDefaultDeviceMessengers() + { + bool messengerAdded = false; + + var allDevices = DeviceManager.AllDevices.Where((d) => !(d is IEssentialsRoom)); + + this.LogInformation( + "All Devices that aren't rooms count: {0}", + allDevices?.Count() + ); + + var count = allDevices.Count(); + + foreach (var device in allDevices) { - this.LogDebug("Adding GenericMessenger for {deviceKey}", device.Key); - AddDefaultDeviceMessenger(new GenericMessenger( - $"{device.Key}-{Key}-generic", - device, - $"/device/{device.Key}")); + try + { + this.LogVerbose( + "Attempting to set up device messengers for {deviceKey}", + device.Key + ); + + // StatusMonitorBase which is prop of ICommunicationMonitor is not a PepperDash.Core.Device, but is in the device array + if (device is ICommunicationMonitor) + { + this.LogVerbose( + "Checking if {deviceKey} implements ICommunicationMonitor", + device.Key + ); + + if (!(device is ICommunicationMonitor commMonitor)) + { + this.LogDebug( + "{deviceKey} does not implement ICommunicationMonitor. Skipping CommunicationMonitorMessenger", + device.Key + ); + + this.LogDebug("Created all messengers for {deviceKey}. Devices Left: {deviceCount}", device.Key, --count); + + continue; + } + + this.LogDebug( + "Adding CommunicationMonitorMessenger for {deviceKey}", + device.Key + ); + + var commMessenger = new ICommunicationMonitorMessenger( + $"{device.Key}-commMonitor-{Key}", + string.Format("/device/{0}", device.Key), + commMonitor + ); + + AddDefaultDeviceMessenger(commMessenger); + + messengerAdded = true; + } + + // Default to IHasCameraControls if CameraBase and IHasCameraControls + if (device is CameraBase cameraDevice && !(device is IHasCameraControls)) + { + this.LogVerbose( + "Adding CameraBaseMessenger for {deviceKey}", + device.Key + ); + + var cameraMessenger = new CameraBaseMessenger( + $"{device.Key}-cameraBase-{Key}", + cameraDevice, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(cameraMessenger); + + messengerAdded = true; + } + + if (device is IHasCameraControls cameraControlDev) + { + this.LogVerbose( + "Adding IHasCamerasWithControlMessenger for {deviceKey}", + device.Key + ); + var cameraControlMessenger = new CameraBaseMessenger( + $"{device.Key}-hasCamerasWithControls-{Key}", + cameraControlDev, + $"/device/{device.Key}" + ); + AddDefaultDeviceMessenger(cameraControlMessenger); + messengerAdded = true; + } + + if (device is BlueJeansPc) + { + this.LogVerbose( + "Adding IRunRouteActionMessnger for {deviceKey}", + device.Key + ); + + var routeMessenger = new RunRouteActionMessenger( + $"{device.Key}-runRouteAction-{Key}", + device as BlueJeansPc, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(routeMessenger); + + messengerAdded = true; + } + + if (device is ITvPresetsProvider) + { + this.LogVerbose( + "Trying to cast to ITvPresetsProvider for {deviceKey}", + device.Key + ); + + var presetsDevice = device as ITvPresetsProvider; + + + this.LogVerbose( + "Adding ITvPresetsProvider for {deviceKey}", + device.Key + ); + + var presetsMessenger = new DevicePresetsModelMessenger( + $"{device.Key}-presets-{Key}", + $"/device/{device.Key}", + presetsDevice + ); + + AddDefaultDeviceMessenger(presetsMessenger); + + messengerAdded = true; + } + + + if (device is DisplayBase) + { + this.LogVerbose("Adding actions for device: {0}", device.Key); + + var dbMessenger = new DisplayBaseMessenger( + $"{device.Key}-displayBase-{Key}", + $"/device/{device.Key}", + device as DisplayBase + ); + + AddDefaultDeviceMessenger(dbMessenger); + + messengerAdded = true; + } + + if (device is TwoWayDisplayBase twoWayDisplay) + { + this.LogVerbose( + "Adding TwoWayDisplayBase for {deviceKey}", + device.Key + ); + var twoWayDisplayMessenger = new TwoWayDisplayBaseMessenger( + $"{device.Key}-twoWayDisplay-{Key}", + string.Format("/device/{0}", device.Key), + twoWayDisplay + ); + AddDefaultDeviceMessenger(twoWayDisplayMessenger); + + messengerAdded = true; + } + + if (device is IBasicVolumeControls) + { + var deviceKey = device.Key; + this.LogVerbose( + "Adding IBasicVolumeControls for {deviceKey}", + deviceKey + ); + + var volControlDevice = device as IBasicVolumeControls; + var messenger = new DeviceVolumeMessenger( + $"{device.Key}-volume-{Key}", + string.Format("/device/{0}", deviceKey), + volControlDevice + ); + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IBasicVideoMuteWithFeedback) + { + var deviceKey = device.Key; + this.LogVerbose( + "Adding IBasicVideoMuteWithFeedback for {deviceKey}", + deviceKey + ); + + var videoMuteControlDevice = device as IBasicVideoMuteWithFeedback; + var messenger = new IBasicVideoMuteWithFeedbackMessenger( + $"{device.Key}-videoMute-{Key}", + string.Format("/device/{0}", deviceKey), + videoMuteControlDevice + ); + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ILightingScenes || device is LightingBase) + { + var deviceKey = device.Key; + + this.LogVerbose( + "Adding LightingBaseMessenger for {deviceKey}", + deviceKey + ); + + var lightingDevice = device as ILightingScenes; + var messenger = new ILightingScenesMessenger( + $"{device.Key}-lighting-{Key}", + lightingDevice, + string.Format("/device/{0}", deviceKey) + ); + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IShadesOpenCloseStop) + { + var deviceKey = device.Key; + var shadeDevice = device as IShadesOpenCloseStop; + + this.LogVerbose( + "Adding ShadeBaseMessenger for {deviceKey}", + deviceKey + ); + + var messenger = new IShadesOpenCloseStopMessenger( + $"{device.Key}-shades-{Key}", + shadeDevice, + string.Format("/device/{0}", deviceKey) + ); + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is VideoCodecBase codec) + { + this.LogVerbose( + "Adding VideoCodecBaseMessenger for {deviceKey}", codec.Key); + + var messenger = new VideoCodecBaseMessenger( + $"{codec.Key}-videoCodec-{Key}", + codec, + $"/device/{codec.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is AudioCodecBase audioCodec) + { + this.LogVerbose( + "Adding AudioCodecBaseMessenger for {deviceKey}", audioCodec.Key + ); + + var messenger = new AudioCodecBaseMessenger( + $"{audioCodec.Key}-audioCodec-{Key}", + audioCodec, + $"/device/{audioCodec.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ISetTopBoxControls stbDevice) + { + this.LogVerbose( + "Adding ISetTopBoxControlMessenger for {deviceKey}" + ); + + var messenger = new ISetTopBoxControlsMessenger( + $"{device.Key}-stb-{Key}", + $"/device/{device.Key}", + stbDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IChannel channelDevice) + { + this.LogVerbose( + "Adding IChannelMessenger for {deviceKey}", device.Key + ); + + var messenger = new IChannelMessenger( + $"{device.Key}-channel-{Key}", + $"/device/{device.Key}", + channelDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IColor colorDevice) + { + this.LogVerbose("Adding IColorMessenger for {deviceKey}", device.Key); + + var messenger = new IColorMessenger( + $"{device.Key}-color-{Key}", + $"/device/{device.Key}", + colorDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IDPad dPadDevice) + { + this.LogVerbose("Adding IDPadMessenger for {deviceKey}", device.Key); + + var messenger = new IDPadMessenger( + $"{device.Key}-dPad-{Key}", + $"/device/{device.Key}", + dPadDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is INumericKeypad nkDevice) + { + this.LogVerbose("Adding INumericKeyapdMessenger for {deviceKey}", device.Key); + + var messenger = new INumericKeypadMessenger( + $"{device.Key}-numericKeypad-{Key}", + $"/device/{device.Key}", + nkDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasPowerControl pcDevice) + { + this.LogVerbose("Adding IHasPowerControlMessenger for {deviceKey}", device.Key); + + var messenger = new IHasPowerMessenger( + $"{device.Key}-powerControl-{Key}", + $"/device/{device.Key}", + pcDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasPowerControlWithFeedback powerControl) + { + var deviceKey = device.Key; + this.LogVerbose( + "Adding IHasPowerControlWithFeedbackMessenger for {deviceKey}", + deviceKey + ); + + var messenger = new IHasPowerControlWithFeedbackMessenger( + $"{device.Key}-powerFeedback-{Key}", + string.Format("/device/{0}", deviceKey), + powerControl + ); + AddDefaultDeviceMessenger(messenger); + messengerAdded = true; + } + + if (device is ITransport transportDevice) + { + this.LogVerbose( + "Adding ITransportMessenger for {deviceKey}", device.Key + ); + + var messenger = new ITransportMessenger( + $"{device.Key}-transport-{Key}", + $"/device/{device.Key}", + transportDevice + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasCurrentSourceInfoChange csiChange) + { + this.LogVerbose("Adding IHasCurrentSourceInfoMessenger for {deviceKey}", device.Key); + + var messenger = new IHasCurrentSourceInfoMessenger( + $"{device.Key}-currentSource-{Key}", + $"/device/{device.Key}", + csiChange + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ICurrentSources currentSources) + { + this.LogVerbose("Adding CurrentSourcesMessenger for {deviceKey}", device.Key); + + var messenger = new CurrentSourcesMessenger($"{device.Key}-currentSources-{Key}", $"/device/{device.Key}", currentSources); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ISwitchedOutput switchedDevice) + { + this.LogVerbose( + "Adding ISwitchedOutputMessenger for {deviceKey}", device.Key + ); + + var messenger = new ISwitchedOutputMessenger( + $"{device.Key}-switchedOutput-{Key}", + switchedDevice, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IDeviceInfoProvider provider) + { + this.LogVerbose("Adding IHasDeviceInfoMessenger for {deviceKey}", device.Key + ); + + var messenger = new DeviceInfoMessenger( + $"{device.Key}-deviceInfo-{Key}", + $"/device/{device.Key}", + provider + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ILevelControls levelControls) + { + this.LogVerbose( + "Adding LevelControlsMessenger for {deviceKey}", device.Key + ); + + var messenger = new ILevelControlsMessenger( + $"{device.Key}-levelControls-{Key}", + $"/device/{device.Key}", + levelControls + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasInputs stringInputs) + { + this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); + + var messenger = new IHasInputsMessenger( + $"{device.Key}-inputs-{Key}", + $"/device/{device.Key}", + stringInputs + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasInputs byteInputs) + { + this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); + + var messenger = new IHasInputsMessenger( + $"{device.Key}-inputs-{Key}", + $"/device/{device.Key}", + byteInputs + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasInputs intInputs) + { + this.LogVerbose("Adding InputsMessenger for {deviceKey}", device.Key); + + var messenger = new IHasInputsMessenger( + $"{device.Key}-inputs-{Key}", + $"/device/{device.Key}", + intInputs + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IMatrixRouting matrix) + { + this.LogVerbose( + "Adding IMatrixRoutingMessenger for {deviceKey}", + device.Key + ); + + var messenger = new IMatrixRoutingMessenger( + $"{device.Key}-matrixRouting", + $"/device/{device.Key}", + matrix + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is ITemperatureSensor tempSensor) + { + this.LogVerbose( + "Adding ITemperatureSensor for {deviceKey}", + device.Key + ); + + var messenger = new ITemperatureSensorMessenger( + $"{device.Key}-tempSensor", + tempSensor, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHumiditySensor humSensor) + { + this.LogVerbose( + "Adding IHumiditySensor for {deviceKey}", + device.Key + ); + + var messenger = new IHumiditySensorMessenger( + $"{device.Key}-humiditySensor", + humSensor, + $"/device/{device.Key}" + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IEssentialsRoomCombiner roomCombiner) + { + this.LogVerbose( + "Adding IEssentialsRoomCombinerMessenger for {deviceKey}", device.Key + ); + + var messenger = new IEssentialsRoomCombinerMessenger( + $"{device.Key}-roomCombiner-{Key}", + $"/device/{device.Key}", + roomCombiner + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IProjectorScreenLiftControl screenLiftControl) + { + this.LogVerbose("Adding IProjectorScreenLiftControlMessenger for {deviceKey}", device.Key + ); + + var messenger = new IProjectorScreenLiftControlMessenger( + $"{device.Key}-screenLiftControl-{Key}", + $"/device/{device.Key}", + screenLiftControl + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IDspPresets dspPresets) + { + this.LogVerbose("Adding IDspPresetsMessenger for {deviceKey}", device.Key + ); + + var messenger = new IDspPresetsMessenger( + $"{device.Key}-dspPresets-{Key}", + $"/device/{device.Key}", + dspPresets + ); + + AddDefaultDeviceMessenger(messenger); + + messengerAdded = true; + } + + if (device is IHasCameras cameras) + { + this.LogVerbose("Adding IHasCamerasMessenger for {deviceKey}", device.Key + ); + var messenger = new IHasCamerasMessenger( + $"{device.Key}-cameras-{Key}", + $"/device/{device.Key}", + cameras + ); + AddDefaultDeviceMessenger(messenger); + messengerAdded = true; + } + + if (device is IHasCamerasWithControls cameras2) + { + this.LogVerbose("Adding IHasCamerasWithControlsMessenger for {deviceKey}", device.Key + ); + var messenger = new IHasCamerasWithControlMessenger( + $"{device.Key}-cameras-{Key}", + $"/device/{device.Key}", + cameras2 + ); + AddDefaultDeviceMessenger(messenger); + messengerAdded = true; + } + + this.LogVerbose("Trying to cast to generic device for device: {key}", device.Key); + + if (device is EssentialsDevice) + { + if (!(device is EssentialsDevice genericDevice) || messengerAdded) + { + this.LogVerbose( + "Skipping GenericMessenger for {deviceKey}. Messenger(s) Added: {messengersAdded}.", + device.Key, + messengerAdded + ); + this.LogDebug( + "AllDevices Completed a device. Devices Left: {count}", + --count + ); + continue; + } + + this.LogDebug( + "Adding GenericMessenger for {deviceKey}", + this, + genericDevice?.Key + ); + + AddDefaultDeviceMessenger( + new GenericMessenger( + genericDevice.Key + "-" + Key + "-generic", + genericDevice, + string.Format("/device/{0}", genericDevice.Key) + ) + ); + } + else + { + this.LogVerbose( + "Not Essentials Device. Skipping GenericMessenger for {deviceKey}", + device.Key + ); + } + this.LogDebug( + "AllDevices Completed a device. Devices Left: {count}", + --count + ); + } + + catch (Exception ex) + { + this.LogException(ex, "Exception setting up default device messengers"); + } } } @@ -515,12 +1299,6 @@ namespace PepperDash.Essentials { simplMessenger.ConfigurationIsReady += Bridge_ConfigurationIsReady; } - - if (messenger is MobileControlBridgeBase roomBridge) - { - _roomBridges.Add(roomBridge); - } - this.LogVerbose( "Adding default messenger with key {messengerKey} for path {messengerPath}", messenger.Key, @@ -543,12 +1321,23 @@ namespace PepperDash.Essentials messenger.MessagePath ); + if (messenger is IMobileControlMessengerWithSubscriptions subMessenger) + { + subMessenger.RegisterWithAppServer(this, Config.EnableMessengerSubscriptions); + return; + } + messenger.RegisterWithAppServer(this); } /// - protected override void Initialize() + public override void Initialize() { + if (!Config.EnableMessengerSubscriptions) + { + this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key); + } + foreach (var messenger in _messengers) { try @@ -575,7 +1364,7 @@ namespace PepperDash.Essentials } } - var simplMessengers = _messengers.Values.OfType().ToList(); + var simplMessengers = _messengers.OfType().ToList(); if (simplMessengers.Count > 0) { @@ -636,9 +1425,8 @@ namespace PepperDash.Essentials _wsClient2.Log.Level = LogLevel.Trace; _wsClient2.SslConfiguration.EnabledSslProtocols = - // System.Security.Authentication.SslProtocols.Tls11 - System.Security.Authentication.SslProtocols.Tls12 - | System.Security.Authentication.SslProtocols.Tls13; + System.Security.Authentication.SslProtocols.Tls11 + | System.Security.Authentication.SslProtocols.Tls12; _wsClient2.OnMessage += HandleMessage; _wsClient2.OnOpen += HandleOpen; @@ -1016,13 +1804,27 @@ namespace PepperDash.Essentials ); } - var expectedAppVersion = ConfigReader.ConfigObject?.Versions?.TouchpanelWrapperApp?.Version; - var userAppPath = Global.FilePathPrefix + "mcUserApp" + Global.DirectorySeparator; - var versionCheck = TouchpanelWrapperAppVersionChecker.CheckDeployedVersion(userAppPath, expectedAppVersion); + var connectedClientVersions = ConnectedClientVersions; - CrestronConsole.ConsoleCommandResponse( - $"\r\nUI Wrapper App Deployed Version Check:\r\n {versionCheck.Summary}\r\n" - ); + if (connectedClientVersions.Count == 0) + { + CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions: None reported yet\r\n"); + } + else + { + CrestronConsole.ConsoleCommandResponse("\r\nUI Client App Versions:\r\n"); + foreach (var kv in connectedClientVersions) + { + var v = kv.Value; + var match = string.IsNullOrEmpty(v.ExpectedAppVersion) || string.Equals(v.ExpectedAppVersion, v.AppVersion, StringComparison.OrdinalIgnoreCase); + + CrestronConsole.ConsoleCommandResponse( + $" Client: {v.ClientId} Touchpanel: {v.TouchpanelKey} Room: {v.RoomKey}\r\n" + + $" Reported: {v.AppVersion} Expected: {(string.IsNullOrEmpty(v.ExpectedAppVersion) ? "(not configured)" : v.ExpectedAppVersion)} Match: {(match ? "Yes" : "NO - MISMATCH")}\r\n" + + $" Last Seen (UTC): {v.LastSeen:yyyy-MM-dd HH:mm:ss}\r\n" + ); + } + } } /// @@ -1052,8 +1854,10 @@ namespace PepperDash.Essentials private void ConnectWebsocketClient() { - lock (_wsCriticalSection) + try { + _wsCriticalSection.Enter(); + // set to 99999 to let things work on 4-Series if ( (CrestronEnvironment.ProgramCompatibility & eCrestronSeries.Series4) @@ -1082,6 +1886,10 @@ namespace PepperDash.Essentials TryConnect(); } + finally + { + _wsCriticalSection.Leave(); + } } private void TryConnect() @@ -1332,17 +2140,13 @@ namespace PepperDash.Essentials private void ResetPingTimer() { // This tells us we're online with the API and getting pings - _pingTimer.Stop(); - _pingTimer.Interval = PingInterval; - _pingTimer.Start(); + _pingTimer.Reset(PingInterval); } private void StartPingTimer() { StopPingTimer(); - _pingTimer = new Timer(PingInterval) { AutoReset = false }; - _pingTimer.Elapsed += (s, e) => PingTimerCallback(null); - _pingTimer.Start(); + _pingTimer = new CTimer(PingTimerCallback, null, PingInterval); } private void StopPingTimer() @@ -1381,9 +2185,10 @@ namespace PepperDash.Essentials private void StartServerReconnectTimer() { StopServerReconnectTimer(); - _serverReconnectTimer = new Timer(ServerReconnectInterval) { AutoReset = false }; - _serverReconnectTimer.Elapsed += (s, e) => ReconnectToServerTimerCallback(null); - _serverReconnectTimer.Start(); + _serverReconnectTimer = new CTimer( + ReconnectToServerTimerCallback, + ServerReconnectInterval + ); this.LogDebug("Reconnect Timer Started."); } @@ -1419,6 +2224,8 @@ namespace PepperDash.Essentials var roomKey = content["roomKey"].Value(); var touchpanelKey = content.SelectToken("touchpanelKey"); + TrackClientAppVersion(clientId, roomKey, touchpanelKey?.Value(), content.SelectToken("appVersion")?.Value()); + if (_roomCombiner == null) { var message = new MobileControlMessage @@ -1490,6 +2297,50 @@ namespace PepperDash.Essentials SendTouchpanelKey(clientId, touchpanelKey); } + /// + /// Records the app version reported by a connecting UI client (e.g. the mobile control React app's + /// build-time APP_VERSION) and compares it against the configured versions.touchpanelWrapperApp version. + /// + private void TrackClientAppVersion(string clientId, string roomKey, string touchpanelKey, string appVersion) + { + if (string.IsNullOrEmpty(appVersion)) + { + return; + } + + var expectedVersion = ConfigReader.ConfigObject?.Versions?.TouchpanelWrapperApp?.Version; + + var info = new ConnectedClientVersionInfo + { + ClientId = clientId, + RoomKey = roomKey, + TouchpanelKey = touchpanelKey, + AppVersion = appVersion, + ExpectedAppVersion = expectedVersion, + LastSeen = DateTime.UtcNow + }; + + lock (_connectedClientVersionsLock) + { + _connectedClientVersions[clientId] = info; + } + + if (!string.IsNullOrEmpty(expectedVersion) && !string.Equals(expectedVersion, appVersion, StringComparison.OrdinalIgnoreCase)) + { + this.LogWarning( + "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}, which does not match configured versions.touchpanelWrapperApp version {expectedVersion}", + clientId, touchpanelKey, appVersion, expectedVersion + ); + } + else + { + this.LogVerbose( + "Client {clientId} (touchpanel {touchpanelKey}) reported UI app version {appVersion}", + clientId, touchpanelKey, appVersion + ); + } + } + private void SendTouchpanelKey(string clientId, JToken touchpanelKeyToken) { if (touchpanelKeyToken == null) @@ -1506,130 +2357,6 @@ namespace PepperDash.Essentials }); } - /// - /// Handles a batch request for device status. Supports two content formats: - /// - /// Granular format: { "devices": { "deviceKey": ["/fullStatus", "/layoutStatus"], ... } } - /// - /// - /// Legacy format: { "deviceKeys": ["deviceKey1", "deviceKey2"] } (equivalent to /fullStatus for each) - /// - /// Triggers the specified action paths for each device in parallel, - /// then sends an /system/initialSyncComplete message after all have been processed. - /// - private void HandleBatchDeviceFullStatus(string clientId, JToken content) - { - if (content == null) - { - this.LogWarning("BatchDeviceFullStatus: content is null"); - return; - } - - // Build a dictionary of deviceKey -> list of action paths - Dictionary> deviceActionPaths; - - var devicesToken = content.SelectToken("devices"); - if (devicesToken != null) - { - // Granular format: { "devices": { "key": ["/path1", "/path2"], ... } } - deviceActionPaths = devicesToken.ToObject>>(); - } - else - { - // Legacy format: { "deviceKeys": ["key1", "key2"] } -> each gets /fullStatus - var deviceKeys = content.SelectToken("deviceKeys")?.ToObject>(); - - if (deviceKeys == null || deviceKeys.Count == 0) - { - this.LogWarning("BatchDeviceFullStatus: No device keys or devices provided"); - SendMessageObject(new MobileControlMessage - { - Type = "/system/initialSyncComplete", - ClientId = clientId - }); - return; - } - - deviceActionPaths = deviceKeys.ToDictionary(k => k, _ => new List { "/fullStatus" }); - } - - if (deviceActionPaths == null || deviceActionPaths.Count == 0) - { - this.LogWarning("BatchDeviceFullStatus: Empty devices dictionary"); - SendMessageObject(new MobileControlMessage - { - Type = "/system/initialSyncComplete", - ClientId = clientId - }); - return; - } - - this.LogInformation("BatchDeviceFullStatus: Processing {count} devices", deviceActionPaths.Count); - - var tasks = new List(); - - foreach (var kvp in deviceActionPaths) - { - var deviceKey = kvp.Key; - var actionPaths = kvp.Value; - - if (actionPaths == null || actionPaths.Count == 0) - { - continue; - } - - foreach (var actionPath in actionPaths) - { - var fullPath = $"/device/{deviceKey}{actionPath}"; - - var handlers = _actionDictionary - .Where(kv => fullPath.StartsWith(kv.Key + "/")) - .SelectMany(kv => kv.Value) - .ToList(); - - if (handlers.Count == 0) - { - this.LogDebug("BatchDeviceFullStatus: No handlers for {deviceKey} at path {actionPath}", deviceKey, actionPath); - continue; - } - - foreach (var handler in handlers) - { - tasks.Add(Task.Run(() => - { - try - { - handler.Action(fullPath, clientId, JToken.FromObject(new { deviceKey })); - } - catch (Exception ex) - { - this.LogError("BatchDeviceFullStatus: Exception in handler for {deviceKey} at {actionPath}: {message}", deviceKey, actionPath, ex.Message); - } - })); - } - } - } - - // After all handlers have completed and enqueued their responses, send sync complete - Task.Run(async () => - { - try - { - await Task.WhenAll(tasks); - } - catch (Exception ex) - { - this.LogError("BatchDeviceFullStatus: Exception waiting for tasks: {message}", ex.Message); - } - - SendMessageObject(new MobileControlMessage - { - Type = "/system/initialSyncComplete", - ClientId = clientId - }); - }); - } - private void SendDeviceInterfaces(string clientId) { this.LogDebug("Sending Device interfaces"); @@ -1732,9 +2459,6 @@ namespace PepperDash.Essentials case "/system/clientJoined": HandleClientJoined(message.Content); break; - case "/system/batchDeviceFullStatus": - HandleBatchDeviceFullStatus(message.ClientId, message.Content); - break; case "/system/reboot": SystemMonitorController.ProcessorReboot(); break; @@ -1875,4 +2599,4 @@ namespace PepperDash.Essentials CrestronConsole.ConsoleCommandResponse("Usage: mobilehttprequest:N get/post url\r"); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj index 2f8ad112..b0e2dd9f 100644 --- a/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj +++ b/src/PepperDash.Essentials.MobileControl/PepperDash.Essentials.MobileControl.csproj @@ -1,7 +1,7 @@  PepperDash.Essentials - net8 + net472 true false epi-essentials-mobile-control @@ -38,8 +38,7 @@ - - + diff --git a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlBridgeBase.cs b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlBridgeBase.cs index 3e2707a8..634255fe 100644 --- a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlBridgeBase.cs +++ b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlBridgeBase.cs @@ -5,126 +5,193 @@ using PepperDash.Essentials.AppServer.Messengers; using PepperDash.Essentials.Core.DeviceTypeInterfaces; -namespace PepperDash.Essentials.RoomBridges; - -/// -/// -/// -public abstract class MobileControlBridgeBase : MessengerBase, IMobileControlRoomMessenger +namespace PepperDash.Essentials.RoomBridges { - public event EventHandler UserCodeChanged; - - public event EventHandler UserPromptedForCode; - - public event EventHandler ClientJoined; - - public event EventHandler AppUrlChanged; - - public IMobileControl Parent { get; private set; } - - public string AppUrl { get; private set; } - public string UserCode { get; private set; } - - public string QrCodeUrl { get; protected set; } - - public string QrCodeChecksum { get; protected set; } - - public string McServerUrl { get; private set; } - - public abstract string RoomName { get; } - - public abstract string RoomKey { get; } - - protected MobileControlBridgeBase(string key, string messagePath) - : base(key, messagePath) - { - } - - protected MobileControlBridgeBase(string key, string messagePath, IKeyName device) - : base(key, messagePath, device) - { - } - /// - /// Set the parent. Does nothing else. Override to add functionality such - /// as adding actions to parent + /// Base class for a Mobile Control Bridge that's used to control a room /// - /// - public virtual void AddParent(IMobileControl parent) + public abstract class MobileControlBridgeBase : MessengerBase, IMobileControlRoomMessenger { - Parent = parent; + /// + /// Triggered when the user Code changes + /// + public event EventHandler UserCodeChanged; - McServerUrl = Parent.ClientAppUrl; - } + /// + /// Triggered when a user should be prompted for the new code + /// + public event EventHandler UserPromptedForCode; - /// - /// Sets the UserCode on the bridge object. Called from controller. A changed code will - /// fire method UserCodeChange. Override that to handle changes - /// - /// - public void SetUserCode(string code) - { - var changed = UserCode != code; - UserCode = code; - if (changed) + /// + /// Triggered when a client joins to control this room + /// + public event EventHandler ClientJoined; + + /// + /// Triggered when the App URL for this room changes + /// + public event EventHandler AppUrlChanged; + + /// + /// Gets or sets the Parent + /// + public IMobileControl Parent { get; private set; } + + /// + /// Gets or sets the AppUrl + /// + public string AppUrl { get; private set; } + /// + /// Gets or sets the UserCode + /// + public string UserCode { get; private set; } + + /// + /// Gets or sets the QrCodeUrl + /// + public string QrCodeUrl { get; protected set; } + + /// + /// Gets or sets the QrCodeChecksum + /// + public string QrCodeChecksum { get; protected set; } + + /// + /// Gets or sets the McServerUrl + /// + public string McServerUrl { get; private set; } + + /// + /// Room Name + /// + public abstract string RoomName { get; } + + /// + /// Room key + /// + public abstract string RoomKey { get; } + + /// + /// Create an instance of the class + /// + /// The unique key for this bridge + /// The message path for this bridge + protected MobileControlBridgeBase(string key, string messagePath) + : base(key, messagePath) { - UserCodeChange(); + } + + /// + /// Create an instance of the class + /// + /// The unique key for this bridge + /// The message path for this bridge + /// The device associated with this bridge + protected MobileControlBridgeBase(string key, string messagePath, IKeyName device) + : base(key, messagePath, device) + { + } + + /// + /// Set the parent. Does nothing else. Override to add functionality such + /// as adding actions to parent + /// + /// + /// + /// AddParent method + /// + public virtual void AddParent(IMobileControl parent) + { + Parent = parent; + + McServerUrl = Parent.ClientAppUrl; + } + + /// + /// Sets the UserCode on the bridge object. Called from controller. A changed code will + /// fire method UserCodeChange. Override that to handle changes + /// + /// + /// + /// SetUserCode method + /// + public void SetUserCode(string code) + { + var changed = UserCode != code; + UserCode = code; + if (changed) + { + UserCodeChange(); + } + } + + + /// + /// Sets the UserCode on the bridge object. Called from controller. A changed code will + /// fire method UserCodeChange. Override that to handle changes + /// + /// + /// Checksum of the QR code. Used for Cisco codec branding command + public void SetUserCode(string code, string qrChecksum) + { + QrCodeChecksum = qrChecksum; + + SetUserCode(code); + } + + /// + /// Update the App Url with the provided URL + /// + /// The new App URL + public virtual void UpdateAppUrl(string url) + { + AppUrl = url; + + var handler = AppUrlChanged; + + if (handler == null) return; + + handler(this, new EventArgs()); + } + + /// + /// Empty method in base class. Override this to add functionality + /// when code changes + /// + protected virtual void UserCodeChange() + { + this.LogDebug("Server user code changed: {userCode}", UserCode); + + var qrUrl = string.Format($"{Parent.Host}/api/rooms/{Parent.SystemUuid}/{RoomKey}/qr?x={new Random().Next()}"); + QrCodeUrl = qrUrl; + + this.LogDebug("Server user code changed: {userCode} - {qrCodeUrl}", UserCode, qrUrl); + + OnUserCodeChanged(); + } + + /// + /// Trigger the UserCodeChanged event + /// + protected void OnUserCodeChanged() + { + UserCodeChanged?.Invoke(this, new EventArgs()); + } + + /// + /// Trigger the UserPromptedForCode event + /// + protected void OnUserPromptedForCode() + { + UserPromptedForCode?.Invoke(this, new EventArgs()); + } + + /// + /// Trigger the ClientJoined event + /// + protected void OnClientJoined() + { + ClientJoined?.Invoke(this, new EventArgs()); } } - - - /// - /// Sets the UserCode on the bridge object. Called from controller. A changed code will - /// fire method UserCodeChange. Override that to handle changes - /// - /// - /// Checksum of the QR code. Used for Cisco codec branding command - public void SetUserCode(string code, string qrChecksum) - { - QrCodeChecksum = qrChecksum; - - SetUserCode(code); - } - - public virtual void UpdateAppUrl(string url) - { - AppUrl = url; - - var handler = AppUrlChanged; - - if (handler == null) return; - - handler(this, new EventArgs()); - } - - /// - /// Empty method in base class. Override this to add functionality - /// when code changes - /// - protected virtual void UserCodeChange() - { - this.LogDebug("Server user code changed: {userCode}", UserCode); - - var qrUrl = string.Format($"{Parent.Host}/api/rooms/{Parent.SystemUuid}/{RoomKey}/qr?x={new Random().Next()}"); - QrCodeUrl = qrUrl; - - this.LogDebug("Server user code changed: {userCode} - {qrCodeUrl}", UserCode, qrUrl); - - OnUserCodeChanged(); - } - - protected void OnUserCodeChanged() - { - UserCodeChanged?.Invoke(this, new EventArgs()); - } - - protected void OnUserPromptedForCode() - { - UserPromptedForCode?.Invoke(this, new EventArgs()); - } - - protected void OnClientJoined() - { - ClientJoined?.Invoke(this, new EventArgs()); - } } \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlEssentialsRoomBridge.cs b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlEssentialsRoomBridge.cs index 22143a51..742251db 100644 --- a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlEssentialsRoomBridge.cs +++ b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlEssentialsRoomBridge.cs @@ -107,7 +107,7 @@ namespace PepperDash.Essentials.RoomBridges // Source Changes and room off - AddAction("/fullStatus", (id, content) => + AddAction("/status", (id, content) => { SendFullStatusForClientId(id, Room); }); @@ -140,6 +140,25 @@ namespace PepperDash.Essentials.RoomBridges if (Room is IRunDefaultPresentRoute defaultRoom) AddAction("/defaultsource", (id, content) => defaultRoom.RunDefaultPresentRoute()); + if (Room is IHasCurrentSourceInfoChange sscRoom) + sscRoom.CurrentSourceChange += Room_CurrentSingleSourceChange; + + if (Room is IEssentialsHuddleVtc1Room vtcRoom) + { + if (vtcRoom.ScheduleSource != null) + { + var key = vtcRoom.Key + "-" + Key; + + if (!AppServerController.CheckForDeviceMessenger(key)) + { + var scheduleMessenger = new IHasScheduleAwarenessMessenger(key, vtcRoom.ScheduleSource, + $"/room/{vtcRoom.Key}"); + AppServerController.AddDeviceMessenger(scheduleMessenger); + } + } + + vtcRoom.InCallFeedback.OutputChange += InCallFeedback_OutputChange; + } if (Room is IPrivacy privacyRoom) { @@ -350,9 +369,9 @@ namespace PepperDash.Essentials.RoomBridges string shareText; bool isSharing; - if (Room is IHasVideoCodec vcRoom && vcRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue) + if (Room is IHasCurrentSourceInfoChange srcInfoRoom && Room is IHasVideoCodec vcRoom && vcRoom.VideoCodec.SharingContentIsOnFeedback.BoolValue && srcInfoRoom.CurrentSourceInfo != null) { - shareText = "Sharing"; + shareText = srcInfoRoom.CurrentSourceInfo.PreferredName; isSharing = true; } else @@ -467,7 +486,18 @@ namespace PepperDash.Essentials.RoomBridges } + private void Room_CurrentSingleSourceChange(SourceListItem info, ChangeType type) + { + /* Example message + * { +   "type":"/room/status", +   "content": { +     "selectedSourceKey": "off", +   } + } + */ + } /// /// Sends the full status of the room to the server @@ -498,7 +528,7 @@ namespace PepperDash.Essentials.RoomBridges { this.LogVerbose("GetFullStatus"); - var sourceKey = (string)null; + var sourceKey = room is IHasCurrentSourceInfoChange ? (room as IHasCurrentSourceInfoChange).CurrentSourceInfoKey : null; var volumes = new Dictionary(); if (room is IHasCurrentVolumeControls rmVc) @@ -527,6 +557,11 @@ namespace PepperDash.Essentials.RoomBridges IsCoolingDown = room.IsCoolingDownFeedback.BoolValue }; + if (room is IEssentialsHuddleVtc1Room vtcRoom) + { + state.IsInCall = vtcRoom.InCallFeedback.BoolValue; + } + return state; } catch (Exception ex) @@ -584,6 +619,23 @@ namespace PepperDash.Essentials.RoomBridges configuration.HelpMessage = propertiesConfig.PropertiesConfig.HelpMessageForDisplay; } + if (room is IEssentialsHuddleSpaceRoom huddleRoom && !string.IsNullOrEmpty(huddleRoom.PropertiesConfig.HelpMessageForDisplay)) + { + this.LogVerbose("Getting huddle room config"); + configuration.HelpMessage = huddleRoom.PropertiesConfig.HelpMessageForDisplay; + configuration.UiBehavior = huddleRoom.PropertiesConfig.UiBehavior; + configuration.DefaultPresentationSourceKey = huddleRoom.PropertiesConfig.DefaultSourceItem; + + } + + if (room is IEssentialsHuddleVtc1Room vtc1Room && !string.IsNullOrEmpty(vtc1Room.PropertiesConfig.HelpMessageForDisplay)) + { + this.LogVerbose("Getting vtc room config"); + configuration.HelpMessage = vtc1Room.PropertiesConfig.HelpMessageForDisplay; + configuration.UiBehavior = vtc1Room.PropertiesConfig.UiBehavior; + configuration.DefaultPresentationSourceKey = vtc1Room.PropertiesConfig.DefaultSourceItem; + } + if (room is IEssentialsTechRoom techRoom && !string.IsNullOrEmpty(techRoom.PropertiesConfig.HelpMessage)) { this.LogVerbose("Getting tech room config"); @@ -667,6 +719,29 @@ namespace PepperDash.Essentials.RoomBridges } } + if (room is IHasDefaultDisplay defDisplayRoom) + { + this.LogVerbose("Getting default display config"); + configuration.DefaultDisplayKey = defDisplayRoom.DefaultDisplay.Key; + configuration.Destinations.Add(eSourceListItemDestinationTypes.defaultDisplay, defDisplayRoom.DefaultDisplay.Key); + } + + if (room is IHasMultipleDisplays multiDisplayRoom) + { + this.LogVerbose("Getting multiple display config"); + + if (multiDisplayRoom.Displays == null) + { + this.LogVerbose("Displays collection is null"); + } + else + { + this.LogVerbose("Displays collection exists"); + + configuration.Destinations = multiDisplayRoom.Displays.ToDictionary(kv => kv.Key, kv => kv.Value.Key); + } + } + if (room is IHasAccessoryDevices accRoom) { Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Getting accessory devices config", this); @@ -940,6 +1015,13 @@ namespace PepperDash.Essentials.RoomBridges [JsonProperty("defaultDisplayKey", NullValueHandling = NullValueHandling.Ignore)] public string DefaultDisplayKey { get; set; } + /// + /// Gets or sets the destinations dictionary keyed by destination type + /// + [JsonProperty("destinations", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Destinations { get; set; } + + /// /// Gets or sets the EnvironmentalDevices /// @@ -1024,6 +1106,7 @@ namespace PepperDash.Essentials.RoomBridges /// public RoomConfiguration() { + Destinations = new Dictionary(); EnvironmentalDevices = new List(); SourceList = new Dictionary(); TouchpanelKeys = new List(); diff --git a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlSIMPLRoomBridge.cs b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlSIMPLRoomBridge.cs index c2b727ce..85f29983 100644 --- a/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlSIMPLRoomBridge.cs +++ b/src/PepperDash.Essentials.MobileControl/RoomBridges/MobileControlSIMPLRoomBridge.cs @@ -87,7 +87,7 @@ namespace PepperDash.Essentials.Room.MobileControl Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(ipId, "127.0.0.2", Global.ControlSystem); var reg = Eisc.Register(); if (reg != eDeviceRegistrationUnRegistrationResponse.Success) - this.LogWarning("Cannot connect EISC at IPID {0}: \r{1}", ipId, reg); + Debug.Console(0, this, "Cannot connect EISC at IPID {0}: \r{1}", ipId, reg); JoinMap = new MobileControlSIMPLRoomJoinMap(1); @@ -109,7 +109,7 @@ namespace PepperDash.Essentials.Room.MobileControl return; } - this.LogDebug("SIMPL EISC online={0}. Config is ready={1}. Use Essentials Config={2}", + Debug.Console(1, this, "SIMPL EISC online={0}. Config is ready={1}. Use Essentials Config={2}", a.DeviceOnLine, Eisc.BooleanOutput[JoinMap.ConfigIsReady.JoinNumber].BoolValue, Eisc.BooleanOutput[JoinMap.ConfigIsLocal.JoinNumber].BoolValue); @@ -141,9 +141,9 @@ namespace PepperDash.Essentials.Room.MobileControl /// /// CustomActivate method /// - protected override bool CustomActivate() + public override bool CustomActivate() { - this.LogDebug("Final activation. Setting up actions and feedbacks"); + Debug.Console(0, this, "Final activation. Setting up actions and feedbacks"); //SetupFunctions(); //SetupFeedbacks(); @@ -169,6 +169,7 @@ namespace PepperDash.Essentials.Room.MobileControl _directRouteMessenger.JoinMap.PrintJoinMapInfo(); + // TODO: Update Source Bridge to use new JoinMap scheme //_sourceBridge.JoinMap.PrintJoinMapInfo(); }, "printmobilebridge", "Prints MC-SIMPL bridge EISC data", ConsoleAccessLevelEnum.AccessOperator); @@ -181,7 +182,7 @@ namespace PepperDash.Essentials.Room.MobileControl SetupDeviceMessengers(); - this.LogInformation("******* ESSENTIALS CONFIG: \r{0}", + Debug.Console(0, this, "******* ESSENTIALS CONFIG: \r{0}", JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); ConfigurationIsReady?.Invoke(this, new EventArgs()); @@ -496,7 +497,7 @@ namespace PepperDash.Essentials.Room.MobileControl /// private void LoadConfigValues() { - this.LogInformation("Loading configuration from SIMPL EISC bridge"); + Debug.Console(1, this, "Loading configuration from SIMPL EISC bridge"); ConfigIsLoaded = false; var co = ConfigReader.ConfigObject; @@ -518,12 +519,12 @@ namespace PepperDash.Essentials.Room.MobileControl var rm = new DeviceConfig(); if (co.Rooms.Count == 0) { - this.LogInformation("Adding room to config"); + Debug.Console(0, this, "Adding room to config"); co.Rooms.Add(rm); } else { - this.LogInformation("Replacing Room[0] in config"); + Debug.Console(0, this, "Replacing Room[0] in config"); co.Rooms[0] = rm; } rm.Name = Eisc.StringOutput[JoinMap.ConfigRoomName.JoinNumber].StringValue; @@ -619,7 +620,7 @@ namespace PepperDash.Essentials.Room.MobileControl var controllable = Eisc.BooleanOutput[JoinMap.SourceIsControllableJoinStart.JoinNumber + i].BoolValue; var audioSource = Eisc.BooleanOutput[JoinMap.SourceIsAudioSourceJoinStart.JoinNumber + i].BoolValue; - this.LogInformation("Adding source {0} '{1}'", key, name); + Debug.Console(0, this, "Adding source {0} '{1}'", key, name); var sourceKey = Eisc.StringOutput[JoinMap.SourceControlDeviceKeyJoinStart.JoinNumber + i].StringValue; @@ -644,16 +645,16 @@ namespace PepperDash.Essentials.Room.MobileControl // Look to see if this is a device that already exists in Essentials and get it if (existingSourceDevice != null) { - this.LogInformation("Found device with key: {0} in Essentials.", key); + Debug.Console(0, this, "Found device with key: {0} in Essentials.", key); if (existingSourceDevice.Properties.Value(_syntheticDeviceKey)) { - this.LogInformation("Updating previous device config with new values"); + Debug.Console(0, this, "Updating previous device config with new values"); existingSourceDevice = syntheticDevice; } else { - this.LogInformation("Using existing Essentials device (non synthetic)"); + Debug.Console(0, this, "Using existing Essentials device (non synthetic)"); } } else @@ -758,7 +759,7 @@ namespace PepperDash.Essentials.Room.MobileControl SetupDeviceMessengers(); - this.LogDebug("******* CONFIG FROM SIMPL: \r{0}", + Debug.Console(0, this, "******* CONFIG FROM SIMPL: \r{0}", JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); ConfigurationIsReady?.Invoke(this, new EventArgs()); @@ -805,7 +806,7 @@ namespace PepperDash.Essentials.Room.MobileControl continue; } - this.LogInformation("Adding destination {0} - {1}", key, name); + Debug.Console(0, this, "Adding destination {0} - {1}", key, name); eRoutingSignalType parsedType; try @@ -814,7 +815,7 @@ namespace PepperDash.Essentials.Room.MobileControl } catch { - this.LogInformation("Error parsing destination type: {0}", routeType); + Debug.Console(0, this, "Error parsing destination type: {0}", routeType); parsedType = eRoutingSignalType.AudioVideo; } @@ -851,15 +852,15 @@ namespace PepperDash.Essentials.Room.MobileControl if (existingDev != null) { - this.LogInformation("Found device with key: {0} in Essentials.", key); + Debug.Console(0, this, "Found device with key: {0} in Essentials.", key); if (existingDev.Properties.Value(_syntheticDeviceKey)) { - this.LogInformation("Updating previous device config with new values"); + Debug.Console(0, this, "Updating previous device config with new values"); } else { - this.LogInformation("Using existing Essentials device (non synthetic)"); + Debug.Console(0, this, "Using existing Essentials device (non synthetic)"); } } else @@ -900,7 +901,7 @@ namespace PepperDash.Essentials.Room.MobileControl if (DeviceManager.GetDeviceForKey(messengerKey) != null) { - this.LogDebug("Messenger with key: {0} already exists. Skipping...", messengerKey); + Debug.Console(2, this, "Messenger with key: {0} already exists. Skipping...", messengerKey); continue; } @@ -908,7 +909,7 @@ namespace PepperDash.Essentials.Room.MobileControl if (dev == null) { - this.LogDebug("Unable to find device config for key: '{0}'", props.DeviceKey); + Debug.Console(1, this, "Unable to find device config for key: '{0}'", props.DeviceKey); continue; } @@ -917,13 +918,13 @@ namespace PepperDash.Essentials.Room.MobileControl if (type.Equals("simplcameramessenger")) { - this.LogDebug("Adding SIMPLCameraMessenger for: '{0}'", props.DeviceKey); + Debug.Console(2, this, "Adding SIMPLCameraMessenger for: '{0}'", props.DeviceKey); messenger = new SIMPLCameraMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey, props.JoinStart); } else if (type.Equals("simplroutemessenger")) { - this.LogDebug("Adding SIMPLRouteMessenger for: '{0}'", props.DeviceKey); + Debug.Console(2, this, "Adding SIMPLRouteMessenger for: '{0}'", props.DeviceKey); messenger = new SIMPLRouteMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey, props.JoinStart); } @@ -936,7 +937,7 @@ namespace PepperDash.Essentials.Room.MobileControl } else { - this.LogDebug("Unable to add messenger for device: '{0}' of type: '{1}'", + Debug.Console(2, this, "Unable to add messenger for device: '{0}' of type: '{1}'", props.DeviceKey, type); } } @@ -949,7 +950,7 @@ namespace PepperDash.Essentials.Room.MobileControl if (dev is CameraBase) { var camDevice = dev as CameraBase; - this.LogDebug("Adding CameraBaseMessenger for device: {0}", dev.Key); + Debug.Console(1, this, "Adding CameraBaseMessenger for device: {0}", dev.Key); var cameraMessenger = new CameraBaseMessenger(device.Key + "-" + Key, camDevice, "/device/" + device.Key); DeviceMessengers.Add(device.Key, cameraMessenger); @@ -963,8 +964,7 @@ namespace PepperDash.Essentials.Room.MobileControl } catch (Exception e) { - this.LogException(e,"Error Setting up Device Managers: {0}", e.Message); - this.LogVerbose("Stack Trace:\r{0}", e.StackTrace); + Debug.Console(2, this, "Error Setting up Device Managers: {0}", e); } } @@ -977,7 +977,7 @@ namespace PepperDash.Essentials.Room.MobileControl { var count = Eisc.UShortOutput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue; - this.LogDebug("The Fader Count is : {0}", count); + Debug.Console(1, this, "The Fader Count is : {0}", count); // build volumes object, serialize and put in content of method below @@ -1013,6 +1013,8 @@ namespace PepperDash.Essentials.Room.MobileControl NumberOfAuxFaders = Eisc.UShortInput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue }; + // TODO: Add property to status message to indicate if advanced sharing is supported and if users can change share mode + PostStatus(new { activityMode = GetActivityMode(), @@ -1080,7 +1082,7 @@ namespace PepperDash.Essentials.Room.MobileControl private void EISC_SigChange(object currentDevice, SigEventArgs args) { if (Debug.Level >= 1) - this.LogVerbose("SIMPL EISC change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, + Debug.Console(1, this, "SIMPL EISC change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, args.Sig.StringValue); var uo = args.Sig.UserObject; if (uo != null) @@ -1120,12 +1122,12 @@ namespace PepperDash.Essentials.Room.MobileControl protected override void UserCodeChange() { - this.LogDebug("Server user code changed: {0}", UserCode); + Debug.Console(1, this, "Server user code changed: {0}", UserCode); var qrUrl = string.Format("{0}/api/rooms/{1}/{3}/qr?x={2}", AppServerController.Host, AppServerController.SystemUuid, new Random().Next(), "room1"); QrCodeUrl = qrUrl; - this.LogDebug("Server user code changed: {0} - {1}", UserCode, qrUrl); + Debug.Console(1, this, "Server user code changed: {0} - {1}", UserCode, qrUrl); OnUserCodeChanged(); diff --git a/src/PepperDash.Essentials.MobileControl/Services/MobileControlApiService.cs b/src/PepperDash.Essentials.MobileControl/Services/MobileControlApiService.cs index f3b02bb6..508fb776 100644 --- a/src/PepperDash.Essentials.MobileControl/Services/MobileControlApiService.cs +++ b/src/PepperDash.Essentials.MobileControl/Services/MobileControlApiService.cs @@ -3,74 +3,89 @@ using System.Net.Http; using System.Threading.Tasks; using PepperDash.Core; -namespace PepperDash.Essentials.Services; - - -public class MobileControlApiService +namespace PepperDash.Essentials.Services { - private readonly HttpClient _client; - public MobileControlApiService(string apiUrl) + /// + /// Service for interacting with a Mobile Control Edge server instance + /// + public class MobileControlApiService { - var handler = new HttpClientHandler + private readonly HttpClient _client; + + /// + /// Create an instance of the class. + /// + /// Mobile Control Edge API URL + public MobileControlApiService(string apiUrl) { - AllowAutoRedirect = false, - ServerCertificateCustomValidationCallback = (req, cert, certChain, errors) => true - }; - - _client = new HttpClient(handler); - } - - public async Task SendAuthorizationRequest(string apiUrl, string grantCode, string systemUuid) - { - try - { - var request = new HttpRequestMessage(HttpMethod.Get, $"{apiUrl}/system/{systemUuid}/authorize?grantCode={grantCode}"); - - Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Sending authorization request to {host}", null, request.RequestUri); - - var response = await _client.SendAsync(request); - - var authResponse = new AuthorizationResponse + var handler = new HttpClientHandler { - Authorized = response.StatusCode == System.Net.HttpStatusCode.OK + AllowAutoRedirect = false, + ServerCertificateCustomValidationCallback = (req, cert, certChain, errors) => true }; - if (authResponse.Authorized) - { - return authResponse; - } - - if (response.StatusCode == System.Net.HttpStatusCode.Moved) - { - var location = response.Headers.Location; - - authResponse.Reason = $"ERROR: Mobile Control API has moved. Please adjust configuration to \"{location}\""; - - return authResponse; - } - - var responseString = await response.Content.ReadAsStringAsync(); - - switch (responseString) - { - case "codeNotFound": - authResponse.Reason = $"Authorization failed. Code not found for system UUID {systemUuid}"; - break; - case "uuidNotFound": - authResponse.Reason = $"Authorization failed. System UUID {systemUuid} not found. Check Essentials configuration."; - break; - default: - authResponse.Reason = $"Authorization failed. Response {response.StatusCode}: {responseString}"; - break; - } - - return authResponse; + _client = new HttpClient(handler); } - catch (Exception ex) + + /// + /// Send authorization request to Mobile Control Edge Server + /// + /// Mobile Control Edge API URL + /// Grant code for authorization + /// System UUID for authorization + /// Authorization response + public async Task SendAuthorizationRequest(string apiUrl, string grantCode, string systemUuid) { - Debug.LogMessage(ex, "Error authorizing with Mobile Control"); - return new AuthorizationResponse { Authorized = false, Reason = ex.Message }; + try + { + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiUrl}/system/{systemUuid}/authorize?grantCode={grantCode}"); + + Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Sending authorization request to {host}", null, request.RequestUri); + + var response = await _client.SendAsync(request); + + var authResponse = new AuthorizationResponse + { + Authorized = response.StatusCode == System.Net.HttpStatusCode.OK + }; + + if (authResponse.Authorized) + { + return authResponse; + } + + if (response.StatusCode == System.Net.HttpStatusCode.Moved) + { + var location = response.Headers.Location; + + authResponse.Reason = $"ERROR: Mobile Control API has moved. Please adjust configuration to \"{location}\""; + + return authResponse; + } + + var responseString = await response.Content.ReadAsStringAsync(); + + switch (responseString) + { + case "codeNotFound": + authResponse.Reason = $"Authorization failed. Code not found for system UUID {systemUuid}"; + break; + case "uuidNotFound": + authResponse.Reason = $"Authorization failed. System UUID {systemUuid} not found. Check Essentials configuration."; + break; + default: + authResponse.Reason = $"Authorization failed. Response {response.StatusCode}: {responseString}"; + break; + } + + return authResponse; + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Error authorizing with Mobile Control"); + return new AuthorizationResponse { Authorized = false, Reason = ex.Message }; + } } } } diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITheme.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITheme.cs index 827331db..c7efbf4c 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITheme.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITheme.cs @@ -1,21 +1,21 @@ using PepperDash.Core; -namespace PepperDash.Essentials.Touchpanel; - -/// -/// Defines the contract for ITheme -/// -public interface ITheme : IKeyed +namespace PepperDash.Essentials.Touchpanel { /// - /// Current theme + /// Defines the contract for ITheme /// - string Theme { get; } + public interface ITheme : IKeyed + { + /// + /// Current theme + /// + string Theme { get; } - /// - /// Set the theme with the given value - /// - /// The theme to set - void UpdateTheme(string theme); + /// + /// Set the theme with the given value + /// + /// The theme to set + void UpdateTheme(string theme); + } } - diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControl.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControl.cs index 35a7d37e..055ad80c 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControl.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControl.cs @@ -1,53 +1,52 @@ using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Touchpanel; - - -/// -/// Defines the contract for ITswAppControl -/// -public interface ITswAppControl : IKeyed +namespace PepperDash.Essentials.Touchpanel { /// - /// Updates when the Zoom Room Control Application opens or closes + /// Defines the contract for ITswAppControl /// - BoolFeedback AppOpenFeedback { get; } + public interface ITswAppControl : IKeyed + { + /// + /// Updates when the Zoom Room Control Application opens or closes + /// + BoolFeedback AppOpenFeedback { get; } + + /// + /// Hide the Zoom App and show the User Control Application + /// + void HideOpenApp(); + + /// + /// Close the Zoom App and show the User Control Application + /// + void CloseOpenApp(); + + /// + /// Open the Zoom App + /// + void OpenApp(); + } /// - /// Hide the Zoom App and show the User Control Application + /// Defines the contract for ITswZoomControl /// - void HideOpenApp(); + public interface ITswZoomControl : IKeyed + { + /// + /// Updates when Zoom has an incoming call + /// + BoolFeedback ZoomIncomingCallFeedback { get; } - /// - /// Close the Zoom App and show the User Control Application - /// - void CloseOpenApp(); + /// + /// Updates when Zoom is in a call + /// + BoolFeedback ZoomInCallFeedback { get; } - /// - /// Open the Zoom App - /// - void OpenApp(); + /// + /// End a Zoom Call + /// + void EndZoomCall(); + } } - -/// -/// Defines the contract for ITswZoomControl -/// -public interface ITswZoomControl : IKeyed -{ - /// - /// Updates when Zoom has an incoming call - /// - BoolFeedback ZoomIncomingCallFeedback { get; } - - /// - /// Updates when Zoom is in a call - /// - BoolFeedback ZoomInCallFeedback { get; } - - /// - /// End a Zoom Call - /// - void EndZoomCall(); -} - diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs index a4e7739e..acfcab2a 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswAppControlMessenger.cs @@ -72,4 +72,4 @@ namespace PepperDash.Essentials.Touchpanel [JsonProperty("appOpen", NullValueHandling = NullValueHandling.Ignore)] public bool? AppOpen { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswZoomControlMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswZoomControlMessenger.cs index 61a3ebbd..bbf4030e 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswZoomControlMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ITswZoomControlMessenger.cs @@ -92,4 +92,4 @@ namespace PepperDash.Essentials.Touchpanel [JsonProperty("incomingCall", NullValueHandling = NullValueHandling.Ignore)] public bool? IncomingCall { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs index d640f22e..262cb34e 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Text.RegularExpressions; using Crestron.SimplSharp; @@ -26,11 +25,28 @@ namespace PepperDash.Essentials.Touchpanel /// Mobile Control touchpanel controller that provides app control, Zoom integration, /// and mobile control functionality for Crestron touchpanels. /// - public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme + public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme, ICommunicationMonitor { private readonly MobileControlTouchpanelProperties localConfig; private IMobileControlRoomMessenger _bridge; + /// + /// Gets the CommunicationMonitor tracking the panel's online/offline state + /// + public StatusMonitorBase CommunicationMonitor { get; private set; } + + private sealed class NullCommunicationMonitor : StatusMonitorBase + { + public NullCommunicationMonitor(IKeyed parent) : base(parent, 120000, 300000) + { + Status = MonitorStatus.InError; + Message = "Panel is not initialized"; + } + + public override void Start() { } + public override void Stop() { } + } + private string _appUrl; /// @@ -42,25 +58,6 @@ namespace PepperDash.Essentials.Touchpanel private readonly StringFeedback McServerUrlFeedback; private readonly StringFeedback UserCodeFeedback; - private string _customLogoUrlLightBkgnd; - private string _customLogoUrlDarkBkgnd; - - /// - /// Gets the CustomLogoUrlLightBkgndFeedback. Reports the absolute URL of the room's custom logo - /// meant to be displayed on a light background (as configured on the room device via - /// , served from the program's own slot-specific - /// logo folder), or an empty string if none is configured. - /// - public StringFeedback CustomLogoUrlLightBkgndFeedback { get; private set; } - - /// - /// Gets the CustomLogoUrlDarkBkgndFeedback. Reports the absolute URL of the room's custom logo - /// meant to be displayed on a dark background (as configured on the room device via - /// , served from the program's own slot-specific - /// logo folder), or an empty string if none is configured. - /// - public StringFeedback CustomLogoUrlDarkBkgndFeedback { get; private set; } - private readonly BoolFeedback _appOpenFeedback; /// @@ -148,6 +145,11 @@ namespace PepperDash.Essentials.Touchpanel { localConfig = config; + if (panel != null) + { + CommunicationMonitor = new CrestronGenericBaseCommunicationMonitor(this, panel, 120000, 300000); + } + AddPostActivationAction(SubscribeForMobileControlUpdates); ThemeFeedback = new StringFeedback($"{Key}-theme", () => Theme); @@ -155,8 +157,6 @@ namespace PepperDash.Essentials.Touchpanel QrCodeUrlFeedback = new StringFeedback($"{Key}-qrCodeUrl", () => _bridge?.QrCodeUrl); McServerUrlFeedback = new StringFeedback($"{Key}-mcServerUrl", () => _bridge?.McServerUrl); UserCodeFeedback = new StringFeedback($"{Key}-userCode", () => _bridge?.UserCode); - CustomLogoUrlLightBkgndFeedback = new StringFeedback($"{Key}-customLogoUrlLightBkgnd", () => _customLogoUrlLightBkgnd); - CustomLogoUrlDarkBkgndFeedback = new StringFeedback($"{Key}-customLogoUrlDarkBkgnd", () => _customLogoUrlDarkBkgnd); _appOpenFeedback = new BoolFeedback($"{Key}-appOpen", () => { @@ -207,7 +207,7 @@ namespace PepperDash.Essentials.Touchpanel Feedbacks = new FeedbackCollection { - AppUrlFeedback, QrCodeUrlFeedback, McServerUrlFeedback, UserCodeFeedback, CustomLogoUrlLightBkgndFeedback, CustomLogoUrlDarkBkgndFeedback + AppUrlFeedback, QrCodeUrlFeedback, McServerUrlFeedback, UserCodeFeedback }; ZoomFeedbacks = new FeedbackCollection { @@ -386,8 +386,10 @@ namespace PepperDash.Essentials.Touchpanel /// /// CustomActivate method /// - protected override bool CustomActivate() + public override bool CustomActivate() { + CommunicationMonitor?.Start(); + var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this); var zoomMessenger = new ITswZoomControlMessenger($"zoomControlMessenger-{Key}", $"/device/{Key}", this); @@ -415,6 +417,17 @@ namespace PepperDash.Essentials.Touchpanel return base.CustomActivate(); } + /// + /// Stops the CommunicationMonitor on deactivation. + /// + /// True if deactivation was successful; otherwise, false. + public override bool Deactivate() + { + CommunicationMonitor?.Stop(); + + return base.Deactivate(); + } + /// /// Handles device extender signal changes for system reserved signals. /// @@ -435,8 +448,6 @@ namespace PepperDash.Essentials.Touchpanel QrCodeUrlFeedback.LinkInputSig(Panel.StringInput[2]); McServerUrlFeedback.LinkInputSig(Panel.StringInput[3]); UserCodeFeedback.LinkInputSig(Panel.StringInput[4]); - CustomLogoUrlLightBkgndFeedback.LinkInputSig(Panel.StringInput[5]); - CustomLogoUrlDarkBkgndFeedback.LinkInputSig(Panel.StringInput[6]); Panel.IpInformationChange -= Panel_IpInformationChange; Panel.IpInformationChange += Panel_IpInformationChange; @@ -462,8 +473,6 @@ namespace PepperDash.Essentials.Touchpanel Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue; Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue; Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue; - Panel.StringInput[5].StringValue = CustomLogoUrlLightBkgndFeedback.StringValue; - Panel.StringInput[6].StringValue = CustomLogoUrlDarkBkgndFeedback.StringValue; if (Panel is TswXX70Base x70Panel) { @@ -545,15 +554,8 @@ namespace PepperDash.Essentials.Touchpanel return false; }) ? csIpAddress.ToString() : processorIp; - var match = Regex.Match(url, @"^http://([^:/]+):\d+/mc/app/?\?token=.+$"); - if (match.Success) - { - string ipa = match.Groups[1].Value; - // ip will be "192.168.1.100" - } - - // replace ipa with ip but leave the rest of the string intact - var updatedUrl = Regex.Replace(url, @"^http://[^:/]+", $"http://{ip}"); + // replace the host but preserve whatever scheme (http/https) is already present in the URL + var updatedUrl = Regex.Replace(url, @"^(https?)://[^:/]+", $"$1://{ip}"); this.LogVerbose("Updated URL: {updatedUrl}", updatedUrl); @@ -594,57 +596,10 @@ namespace PepperDash.Essentials.Touchpanel { this.LogInformation("AppURL changed: {appURL}", _bridge.AppUrl); SetAppUrl(_bridge.AppUrl); - UpdateCustomLogoUrls(); UpdateFeedbacks(s, a); }; SetAppUrl(_bridge.AppUrl); - UpdateCustomLogoUrls(); - } - - /// - /// Resolves the room's configured custom logos (light and dark background variants, if any) into - /// absolute URLs served from this processor's own slot-specific logo folder, and updates - /// CustomLogoUrlLightBkgndFeedback / CustomLogoUrlDarkBkgndFeedback accordingly. - /// - private void UpdateCustomLogoUrls() - { - var room = DeviceManager.AllDevices.OfType() - .FirstOrDefault(r => r.Key == _config.DefaultRoomKey); - - _customLogoUrlLightBkgnd = ResolveLogoUrl(room?.LogoUrlLightBkgnd); - _customLogoUrlDarkBkgnd = ResolveLogoUrl(room?.LogoUrlDarkBkgnd); - - CustomLogoUrlLightBkgndFeedback.FireUpdate(); - CustomLogoUrlDarkBkgndFeedback.FireUpdate(); - } - - /// - /// Resolves a room-configured logo file path into an absolute URL served from this processor's own - /// slot-specific logo folder (e.g. http://{ip}:{port}/mc/app/logo/{filename}). Returns an empty - /// string if is null/empty, or the app URL can't be determined. - /// - private string ResolveLogoUrl(string customLogoPath) - { - if (string.IsNullOrEmpty(customLogoPath) || string.IsNullOrEmpty(_bridge?.AppUrl)) - { - return string.Empty; - } - - var originMatch = Regex.Match(_bridge.AppUrl, @"^(https?://[^/]+)"); - - if (!originMatch.Success) - { - this.LogWarning("Could not determine origin from AppUrl '{appUrl}', cannot build custom logo URL", _bridge.AppUrl); - return string.Empty; - } - - // Path.GetFileName strips any directory components, matching the server's own image - // request handler, which only ever resolves a bare filename within its logo folder. - var filename = Path.GetFileName(customLogoPath); - var logoUrl = $"{originMatch.Groups[1].Value}/mc/app/logo/{filename}"; - - return GetUrlWithCorrectIp(logoUrl); } /// @@ -656,23 +611,7 @@ namespace PepperDash.Essentials.Touchpanel /// public void SetAppUrl(string url) { - if (string.IsNullOrEmpty(url)) - { - this.LogInformation("AppUrl is null or empty, skipping SetAppUrl"); - return; - } - - if(localConfig.DevelopmentServerAddress != null) - { - url = Regex.Replace(url, @"^http://[^/]+", $"http://{localConfig.DevelopmentServerAddress}"); - this.LogInformation("Using development server IP, updated URL: {url}", url); - } - else - { - url = GetUrlWithCorrectIp(url); - } - - _appUrl = url; + _appUrl = GetUrlWithCorrectIp(url); AppUrlFeedback.FireUpdate(); } @@ -829,7 +768,7 @@ namespace PepperDash.Essentials.Touchpanel /// public MobileControlTouchpanelControllerFactory() { - TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" }; + TypeNames = new List() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mctsw1080", "mcts1080", "mcxpanel", "mcdge1000" }; MinimumEssentialsFrameworkVersion = "2.0.0"; factories = new Dictionary> @@ -854,6 +793,8 @@ namespace PepperDash.Essentials.Touchpanel {"ts770", (id, controlSystem, projectName) => new Ts770(id, controlSystem)}, {"tsw1070", (id, controlSystem, projectName) => new Tsw1070(id, controlSystem)}, {"ts1070", (id, controlSystem, projectName) => new Ts1070(id, controlSystem)}, + {"tsw1080", (id, controlSystem, projectName) => new Tsw1080(id, controlSystem)}, + {"ts1080", (id, controlSystem, projectName) => new Ts1080(id, controlSystem)}, {"dge1000", (id, controlSystem, projectName) => new Dge1000(id, controlSystem)} }; } @@ -907,4 +848,4 @@ namespace PepperDash.Essentials.Touchpanel } } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelProperties.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelProperties.cs index c6658f9f..8156834b 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelProperties.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/MobileControlTouchpanelProperties.cs @@ -1,49 +1,39 @@ - -using Newtonsoft.Json; +using Newtonsoft.Json; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials.Touchpanel; - -/// -/// Represents a MobileControlTouchpanelProperties -/// -public class MobileControlTouchpanelProperties : CrestronTouchpanelPropertiesConfig +namespace PepperDash.Essentials.Touchpanel { - /// - /// Gets or sets the UseDirectServer + /// Represents a MobileControlTouchpanelProperties /// - [JsonProperty("useDirectServer")] - public bool UseDirectServer { get; set; } = false; + public class MobileControlTouchpanelProperties : CrestronTouchpanelPropertiesConfig + { - /// - /// Gets or sets the DevelopmentServerIp. Optional. If UseDirectServer is true, - /// this IP address will be used for direct communication between the mobile control and a development server running on the same local network. - /// This allows development using a Vite server for hot module replacement (HMR) and live reloading during development. - /// Specifying this property will override the default behavior of mobile control. - /// - [JsonProperty("developmentServerAddress", NullValueHandling = NullValueHandling.Ignore)] - public string DevelopmentServerAddress { get; set; } + /// + /// Gets or sets the UseDirectServer + /// + [JsonProperty("useDirectServer")] + public bool UseDirectServer { get; set; } = false; - /// - /// Gets or sets the ZoomRoomController - /// - [JsonProperty("zoomRoomController")] - public bool ZoomRoomController { get; set; } = false; + /// + /// Gets or sets the ZoomRoomController + /// + [JsonProperty("zoomRoomController")] + public bool ZoomRoomController { get; set; } = false; - /// - /// Gets or sets the ButtonToolbarTimoutInS - /// - [JsonProperty("buttonToolbarTimeoutInS")] - public ushort ButtonToolbarTimoutInS { get; set; } = 0; + /// + /// Gets or sets the ButtonToolbarTimoutInS + /// + [JsonProperty("buttonToolbarTimeoutInS")] + public ushort ButtonToolbarTimoutInS { get; set; } = 0; - /// - /// Gets or sets the Theme - /// - [JsonProperty("theme")] - public string Theme { get; set; } = "light"; -} - + /// + /// Gets or sets the Theme + /// + [JsonProperty("theme")] + public string Theme { get; set; } = "light"; + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs index cecd613e..35d85442 100644 --- a/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs +++ b/src/PepperDash.Essentials.MobileControl/Touchpanel/ThemeMessenger.cs @@ -57,4 +57,4 @@ namespace PepperDash.Essentials.Touchpanel [JsonProperty("theme")] public string Theme { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/TouchpanelWrapperAppVersionChecker.cs b/src/PepperDash.Essentials.MobileControl/TouchpanelWrapperAppVersionChecker.cs deleted file mode 100644 index 1368b788..00000000 --- a/src/PepperDash.Essentials.MobileControl/TouchpanelWrapperAppVersionChecker.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.IO; -using System.Linq; - -namespace PepperDash.Essentials -{ - /// - /// Determines whether the touchpanel wrapper app (the mobile control React app) deployed to this - /// processor's mcUserApp folder matches the version configured in the system config's - /// versions.touchpanelWrapperApp. - /// - /// - /// This reads the app's built .js/.html files directly from disk, so unlike the previous - /// client-self-reported-version approach, it does not depend on any UI client being connected and - /// does not accumulate per-client state over the life of the program. - /// - public static class TouchpanelWrapperAppVersionChecker - { - private static readonly string[] SearchPatterns = { "*.js", "*.html" }; - - /// - /// Scans the deployed touchpanel wrapper app's .js/.html files under - /// for the literal string (the version build tooling bakes - /// into the app at build time). - /// - /// The path to the deployed mcUserApp folder - /// The expected version, from config's versions.touchpanelWrapperApp.version - public static TouchpanelWrapperAppVersionCheckResult CheckDeployedVersion(string appPath, string expectedVersion) - { - if (string.IsNullOrEmpty(expectedVersion)) - { - return TouchpanelWrapperAppVersionCheckResult.NotConfigured(appPath); - } - - if (string.IsNullOrEmpty(appPath) || !Directory.Exists(appPath)) - { - return TouchpanelWrapperAppVersionCheckResult.AppNotDeployed(appPath, expectedVersion); - } - - try - { - var files = SearchPatterns - .SelectMany(pattern => Directory.GetFiles(appPath, pattern, SearchOption.AllDirectories)) - .ToList(); - - if (files.Count == 0) - { - return TouchpanelWrapperAppVersionCheckResult.AppNotDeployed(appPath, expectedVersion); - } - - foreach (var file in files) - { - var contents = File.ReadAllText(file); - if (contents.IndexOf(expectedVersion, StringComparison.OrdinalIgnoreCase) >= 0) - { - return TouchpanelWrapperAppVersionCheckResult.Match(appPath, expectedVersion, file); - } - } - - return TouchpanelWrapperAppVersionCheckResult.Mismatch(appPath, expectedVersion, files.Count); - } - catch (Exception ex) - { - return TouchpanelWrapperAppVersionCheckResult.Error(appPath, expectedVersion, ex.Message); - } - } - } - - /// - /// The outcome of a check - /// - public class TouchpanelWrapperAppVersionCheckResult - { - /// - /// The mcUserApp path that was checked - /// - public string AppPath { get; } - - /// - /// The expected version from config, if any was configured - /// - public string ExpectedVersion { get; } - - /// - /// True if any files were found deployed at - /// - public bool AppDeployed { get; } - - /// - /// True if was found in one of the deployed files - /// - public bool VersionMatched { get; } - - /// - /// The file the expected version was found in, if is true - /// - public string MatchedFile { get; } - - /// - /// The number of files scanned - /// - public int FilesScanned { get; } - - /// - /// A human-readable summary of the outcome, suitable for console output - /// - public string Summary { get; } - - private TouchpanelWrapperAppVersionCheckResult(string appPath, string expectedVersion, bool appDeployed, - bool versionMatched, string matchedFile, int filesScanned, string summary) - { - AppPath = appPath; - ExpectedVersion = expectedVersion; - AppDeployed = appDeployed; - VersionMatched = versionMatched; - MatchedFile = matchedFile; - FilesScanned = filesScanned; - Summary = summary; - } - - internal static TouchpanelWrapperAppVersionCheckResult NotConfigured(string appPath) => - new TouchpanelWrapperAppVersionCheckResult(appPath, null, false, false, null, 0, - "versions.touchpanelWrapperApp.version is not configured; skipping check"); - - internal static TouchpanelWrapperAppVersionCheckResult AppNotDeployed(string appPath, string expectedVersion) => - new TouchpanelWrapperAppVersionCheckResult(appPath, expectedVersion, false, false, null, 0, - $"No app files found at '{appPath}'; expected version {expectedVersion}"); - - internal static TouchpanelWrapperAppVersionCheckResult Match(string appPath, string expectedVersion, string matchedFile) => - new TouchpanelWrapperAppVersionCheckResult(appPath, expectedVersion, true, true, matchedFile, 1, - $"Deployed app matches configured version {expectedVersion} (found in '{matchedFile}')"); - - internal static TouchpanelWrapperAppVersionCheckResult Mismatch(string appPath, string expectedVersion, int filesScanned) => - new TouchpanelWrapperAppVersionCheckResult(appPath, expectedVersion, true, false, null, filesScanned, - $"Deployed app at '{appPath}' does NOT contain expected version {expectedVersion} ({filesScanned} file(s) scanned) - MISMATCH"); - - internal static TouchpanelWrapperAppVersionCheckResult Error(string appPath, string expectedVersion, string errorMessage) => - new TouchpanelWrapperAppVersionCheckResult(appPath, expectedVersion, false, false, null, 0, - $"Error checking deployed app version at '{appPath}': {errorMessage}"); - } -} diff --git a/src/PepperDash.Essentials.MobileControl/TransmitMessage.cs b/src/PepperDash.Essentials.MobileControl/TransmitMessage.cs index e08d0c3c..06595a9d 100644 --- a/src/PepperDash.Essentials.MobileControl/TransmitMessage.cs +++ b/src/PepperDash.Essentials.MobileControl/TransmitMessage.cs @@ -13,43 +13,35 @@ namespace PepperDash.Essentials /// public class TransmitMessage : IQueueMessage { - private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - Converters = { new IsoDateTimeConverter() } - }; - private readonly WebSocket _ws; - private readonly string _serializedMessage; + private readonly object msgToSend; /// - /// Initialize a message to send. - /// Serialization occurs here in the caller's thread context rather than on the queue thread. + /// Initialize a message to send /// /// message object to send /// WebSocket instance public TransmitMessage(object msg, WebSocket ws) { _ws = ws; - _serializedMessage = JsonConvert.SerializeObject(msg, Formatting.None, SerializerSettings); + msgToSend = msg; } /// - /// Initialize a message to send. - /// Serialization occurs here in the caller's thread context rather than on the queue thread. + /// Initialize a message to send /// /// message object to send /// WebSocket instance public TransmitMessage(DeviceStateMessageBase msg, WebSocket ws) { _ws = ws; - _serializedMessage = JsonConvert.SerializeObject(msg, Formatting.None, SerializerSettings); + msgToSend = msg; } #region Implementation of IQueueMessage /// - /// Dispatch method - only handles WebSocket send since serialization was done at construction time + /// Dispatch method /// public void Dispatch() { @@ -67,9 +59,13 @@ namespace PepperDash.Essentials return; } - Debug.LogVerbose("Message TX: {0}", _serializedMessage); - _ws.Send(_serializedMessage); + var message = JsonConvert.SerializeObject(msgToSend, Formatting.None, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new IsoDateTimeConverter() } }); + + Debug.LogVerbose("Message TX: {0}", message); + + _ws.Send(message); } catch (Exception ex) { diff --git a/src/PepperDash.Essentials.MobileControl/UserCodeChangedContent.cs b/src/PepperDash.Essentials.MobileControl/UserCodeChangedContent.cs index a7f4e893..ce31f766 100644 --- a/src/PepperDash.Essentials.MobileControl/UserCodeChangedContent.cs +++ b/src/PepperDash.Essentials.MobileControl/UserCodeChangedContent.cs @@ -1,24 +1,24 @@ using Newtonsoft.Json; -namespace PepperDash.Essentials; - -/// -/// Represents a UserCodeChangedContent -/// -public class UserCodeChangedContent +namespace PepperDash.Essentials { - /// - /// Gets or sets the UserCode + /// Represents a UserCodeChangedContent /// - [JsonProperty("userCode")] - public string UserCode { get; set; } + public class UserCodeChangedContent + { + + /// + /// Gets or sets the UserCode + /// + [JsonProperty("userCode")] + public string UserCode { get; set; } - /// - /// Gets or sets the QrChecksum - /// - [JsonProperty("qrChecksum", NullValueHandling = NullValueHandling.Include)] - public string QrChecksum { get; set; } + /// + /// Gets or sets the QrChecksum + /// + [JsonProperty("qrChecksum", NullValueHandling = NullValueHandling.Include)] + public string QrChecksum { get; set; } + } } - diff --git a/src/PepperDash.Essentials.MobileControl/Volumes.cs b/src/PepperDash.Essentials.MobileControl/Volumes.cs index 2d95a675..44febbfc 100644 --- a/src/PepperDash.Essentials.MobileControl/Volumes.cs +++ b/src/PepperDash.Essentials.MobileControl/Volumes.cs @@ -1,137 +1,137 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace PepperDash.Essentials; - - -/// -/// Represents a Volumes -/// -public class Volumes -{ - - /// - /// Gets or sets the Master - /// - [JsonProperty("master", NullValueHandling = NullValueHandling.Ignore)] - public Volume Master { get; set; } - - /// - /// Aux Faders as configured in the room - /// - [JsonProperty("auxFaders", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary AuxFaders { get; set; } - - /// - /// Count of aux faders for this system - /// - [JsonProperty("numberOfAuxFaders", NullValueHandling = NullValueHandling.Ignore)] - public int? NumberOfAuxFaders { get; set; } -} - -/// -/// Represents a Volume -/// -public class Volume +namespace PepperDash.Essentials { /// - /// Gets or sets the Key + /// Represents a Volumes /// - [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] - public string Key { get; set; } - - /// - /// Level for this volume object - /// - [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] - public int? Level { get; set; } - - /// - /// True if this volume control is muted - /// - [JsonProperty("muted", NullValueHandling = NullValueHandling.Ignore)] - public bool? Muted { get; set; } - - - /// - /// Gets or sets the Label - /// - [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] - public string Label { get; set; } - - /// - /// True if this volume object has mute control - /// - [JsonProperty("hasMute", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasMute { get; set; } - - /// - /// True if this volume object has Privacy mute control - /// - [JsonProperty("hasPrivacyMute", NullValueHandling = NullValueHandling.Ignore)] - public bool? HasPrivacyMute { get; set; } - - /// - /// True if the privacy mute is muted - /// - [JsonProperty("privacyMuted", NullValueHandling = NullValueHandling.Ignore)] - public bool? PrivacyMuted { get; set; } - - - - /// - /// Gets or sets the MuteIcon - /// - [JsonProperty("muteIcon", NullValueHandling = NullValueHandling.Ignore)] - public string MuteIcon { get; set; } - - /// - /// Create an instance of the class - /// - /// The key for this volume object - /// The level for this volume object - /// True if this volume control is muted - /// The label for this volume object - /// True if this volume object has mute control - /// The mute icon for this volume object - public Volume(string key, int level, bool muted, string label, bool hasMute, string muteIcon) - : this(key) + public class Volumes { - Level = level; - Muted = muted; - Label = label; - HasMute = hasMute; - MuteIcon = muteIcon; + + /// + /// Gets or sets the Master + /// + [JsonProperty("master", NullValueHandling = NullValueHandling.Ignore)] + public Volume Master { get; set; } + + /// + /// Aux Faders as configured in the room + /// + [JsonProperty("auxFaders", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary AuxFaders { get; set; } + + /// + /// Count of aux faders for this system + /// + [JsonProperty("numberOfAuxFaders", NullValueHandling = NullValueHandling.Ignore)] + public int? NumberOfAuxFaders { get; set; } } /// - /// Create an instance of the class + /// Represents a Volume /// - /// The key for this volume object - /// The level for this volume object - public Volume(string key, int level) - : this(key) + public class Volume { - Level = level; - } + /// + /// Gets or sets the Key + /// + [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] + public string Key { get; set; } - /// - /// Create an instance of the class - /// - /// The key for this volume object - /// True if this volume control is muted - public Volume(string key, bool muted) - : this(key) - { - Muted = muted; - } + /// + /// Level for this volume object + /// + [JsonProperty("level", NullValueHandling = NullValueHandling.Ignore)] + public int? Level { get; set; } - /// - /// Create an instance of the class - /// - /// The key for this volume object - public Volume(string key) - { - Key = key; + /// + /// True if this volume control is muted + /// + [JsonProperty("muted", NullValueHandling = NullValueHandling.Ignore)] + public bool? Muted { get; set; } + + + /// + /// Gets or sets the Label + /// + [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] + public string Label { get; set; } + + /// + /// True if this volume object has mute control + /// + [JsonProperty("hasMute", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasMute { get; set; } + + /// + /// True if this volume object has Privacy mute control + /// + [JsonProperty("hasPrivacyMute", NullValueHandling = NullValueHandling.Ignore)] + public bool? HasPrivacyMute { get; set; } + + /// + /// True if the privacy mute is muted + /// + [JsonProperty("privacyMuted", NullValueHandling = NullValueHandling.Ignore)] + public bool? PrivacyMuted { get; set; } + + + + /// + /// Gets or sets the MuteIcon + /// + [JsonProperty("muteIcon", NullValueHandling = NullValueHandling.Ignore)] + public string MuteIcon { get; set; } + + /// + /// Create an instance of the class + /// + /// The key for this volume object + /// The level for this volume object + /// True if this volume control is muted + /// The label for this volume object + /// True if this volume object has mute control + /// The mute icon for this volume object + public Volume(string key, int level, bool muted, string label, bool hasMute, string muteIcon) + : this(key) + { + Level = level; + Muted = muted; + Label = label; + HasMute = hasMute; + MuteIcon = muteIcon; + } + + /// + /// Create an instance of the class + /// + /// The key for this volume object + /// The level for this volume object + public Volume(string key, int level) + : this(key) + { + Level = level; + } + + /// + /// Create an instance of the class + /// + /// The key for this volume object + /// True if this volume control is muted + public Volume(string key, bool muted) + : this(key) + { + Muted = muted; + } + + /// + /// Create an instance of the class + /// + /// The key for this volume object + public Volume(string key) + { + Key = key; + } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/ActionPathsHandler.cs b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/ActionPathsHandler.cs index 5bf7be4b..1c204e8e 100644 --- a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/ActionPathsHandler.cs +++ b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/ActionPathsHandler.cs @@ -81,4 +81,4 @@ namespace PepperDash.Essentials.WebApiHandlers [JsonProperty("path")] public string Path { get; set; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/DeleteAllUiClientsHandler.cs b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/DeleteAllUiClientsHandler.cs new file mode 100644 index 00000000..fd5f9844 --- /dev/null +++ b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/DeleteAllUiClientsHandler.cs @@ -0,0 +1,41 @@ +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using PepperDash.Core.Web.RequestHandlers; +using PepperDash.Essentials.WebSocketServer; + + +namespace PepperDash.Essentials.WebApiHandlers +{ + /// + /// Represents a DeleteAllUiClientsHandler + /// + public class DeleteAllUiClientsHandler : WebApiBaseRequestHandler + { + private readonly MobileControlWebsocketServer server; + + /// + /// Essentials CWS API handler for the MC Direct Server + /// + /// Direct Server instance + public DeleteAllUiClientsHandler(MobileControlWebsocketServer directServer) : base(true) + { + server = directServer; + } + + /// + /// Deletes all clients from the Direct Server + /// + /// HTTP Context for this request + protected override void HandleDelete(HttpCwsContext context) + { + server.RemoveAllTokens("confirm"); + + var res = context.Response; + res.StatusCode = 200; + res.ContentType = "application/json"; + res.Headers.Add("Content-Type", "application/json"); + res.Write(JsonConvert.SerializeObject(new { success = true }), false); + res.End(); + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileAuthRequestHandler.cs b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileAuthRequestHandler.cs index 850a3e6a..5d9e9766 100644 --- a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileAuthRequestHandler.cs +++ b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileAuthRequestHandler.cs @@ -68,4 +68,4 @@ namespace PepperDash.Essentials.WebApiHandlers } } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileInfoHandler.cs b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileInfoHandler.cs index 551a9b4d..676fd3c8 100644 --- a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileInfoHandler.cs +++ b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/MobileInfoHandler.cs @@ -284,4 +284,4 @@ namespace PepperDash.Essentials.WebApiHandlers this.client = client; } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/UiClientHandler.cs b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/UiClientHandler.cs index 2a7ed8c7..2f972ddf 100644 --- a/src/PepperDash.Essentials.MobileControl/WebApiHandlers/UiClientHandler.cs +++ b/src/PepperDash.Essentials.MobileControl/WebApiHandlers/UiClientHandler.cs @@ -1,4 +1,3 @@ - using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; using PepperDash.Core; diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs index ba6a94fb..c96dc116 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/MobileControlWebsocketServer.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.IO; -using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Security.Authentication; @@ -128,6 +127,16 @@ namespace PepperDash.Essentials.WebSocketServer ///
public int Port { get; private set; } + /// + /// Gets the HTTP scheme to use for generated URLs, based on whether the direct server is configured as secure + /// + private string HttpScheme => _parent.Config.DirectServer.Secure ? "https" : "http"; + + /// + /// Gets the WebSocket scheme to use for generated URLs, based on whether the direct server is configured as secure + /// + private string WsScheme => _parent.Config.DirectServer.Secure ? "wss" : "ws"; + /// /// Gets the user app URL prefix /// @@ -135,7 +144,8 @@ namespace PepperDash.Essentials.WebSocketServer { get { - return string.Format("http://{0}:{1}{2}?token=", + return string.Format("{0}://{1}:{2}{3}?token=", + HttpScheme, CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port, _userAppBaseHref); @@ -239,11 +249,17 @@ namespace PepperDash.Essentials.WebSocketServer var routes = new List { - new HttpCwsRoute($"devices/{Key}/client") + new HttpCwsRoute($"device/{Key}/client") { Name = "ClientHandler", RouteHandler = new UiClientHandler(this) }, + + new HttpCwsRoute($"device/{Key}/deleteAllUiClients") + { + Name = "DeleteAllClientsHandler", + RouteHandler = new DeleteAllUiClientsHandler(this) + }, }; apiServer.AddRoute(routes); @@ -262,30 +278,13 @@ namespace PepperDash.Essentials.WebSocketServer /// Initialize method ///
/// - protected override void Initialize() + public override void Initialize() { try { base.Initialize(); - ServerSslConfiguration sslConfig = null; - - if (_parent.Config.DirectServer.Secure) - { - this.LogInformation("Adding SSL Configuration to server"); - sslConfig = new ServerSslConfiguration(new X509Certificate2($"\\user\\{certificateName}.pfx", certificatePassword)) - { - ClientCertificateRequired = false, - CheckCertificateRevocation = false, - EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13 - }; - } - - _server = new HttpServer(Port, false); - if (sslConfig != null) - { - _server.SslConfiguration.ServerCertificate = sslConfig.ServerCertificate; - } + _server = new HttpServer(Port, _parent.Config.DirectServer.Secure); _server.OnGet += Server_OnGet; @@ -296,6 +295,17 @@ namespace PepperDash.Essentials.WebSocketServer _server.OnPost += Server_OnPost; } + if (_parent.Config.DirectServer.Secure) + { + this.LogInformation("Adding SSL Configuration to server"); + _server.SslConfiguration = new ServerSslConfiguration(new X509Certificate2($"\\user\\{certificateName}.pfx", certificatePassword)) + { + ClientCertificateRequired = false, + CheckCertificateRevocation = false, + EnabledSslProtocols = SslProtocols.Tls12 + }; + } + _server.Log.Output = (data, message) => Utilities.ConvertWebsocketLog(data, message, this); // setting to trace to allow logging level to be controlled by appdebug @@ -356,10 +366,11 @@ namespace PepperDash.Essentials.WebSocketServer foreach (var client in touchpanelsToAdd) { var bridge = _parent.GetRoomBridge(client.DefaultRoomKey); + if (bridge == null) { this.LogWarning("Unable to find room with key: {defaultRoomKey}", client.DefaultRoomKey); - continue; + return; } var (key, path) = GenerateClientToken(bridge, client.Key); @@ -403,11 +414,11 @@ namespace PepperDash.Essentials.WebSocketServer ip = csIpAddress.ToString(); } - var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app/?token={touchpanel.Key}"; + var appUrl = $"{HttpScheme}://{ip}:{Port}/mc/app?token={touchpanel.Key}"; this.LogVerbose("Sending URL {appUrl} to touchpanel {touchpanelKey}", appUrl, touchpanel.Touchpanel.Key); - touchpanel.Touchpanel.SetAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app/?token={touchpanel.Key}"); + touchpanel.Touchpanel.SetAppUrl(appUrl); } } @@ -428,8 +439,6 @@ namespace PepperDash.Essentials.WebSocketServer Directory.CreateDirectory(userAppPath); } - DeployMcUserAppZipIfPresent(); - if (!Directory.Exists($"{userAppPath}{localConfigFolderName}")) { Directory.CreateDirectory($"{userAppPath}{localConfigFolderName}"); @@ -483,118 +492,13 @@ namespace PepperDash.Essentials.WebSocketServer } } - /// - /// Checks the mcUserApp folder for a deployed zip bundle (e.g. dropped there directly by an - /// SFTP deploy script or manual upload) and, if found, wipes the folder's existing contents - /// (except the zip itself) and extracts the zip in its place. This allows a new touchpanel - /// wrapper app build to be deployed just by copying a zip file into the already-existing - /// mcUserApp folder, without needing to separately clear out the previous build first. - /// - private void DeployMcUserAppZipIfPresent() - { - string[] zipFiles; - - try - { - zipFiles = Directory.GetFiles(userAppPath, "*.zip", SearchOption.TopDirectoryOnly); - } - catch (Exception ex) - { - this.LogError("Error scanning {userAppPath} for a deployed mcUserApp zip file: {message}", userAppPath, ex.Message); - this.LogDebug(ex, "Stack Trace"); - return; - } - - if (zipFiles.Length == 0) - { - return; - } - - if (zipFiles.Length > 1) - { - this.LogWarning("Multiple zip files found in {userAppPath}. Using the first one found: {zipFile}", userAppPath, zipFiles[0]); - } - - var zipFilePath = zipFiles[0]; - - try - { - this.LogInformation("Found mcUserApp deployment zip file {zipFile}. Clearing existing app contents and unzipping...", zipFilePath); - - // Remove everything currently in the folder except the zip file itself, so the new - // build doesn't end up mixed in with stale files from a previous deployment. - foreach (var entryPath in Directory.GetFileSystemEntries(userAppPath)) - { - if (string.Equals(entryPath, zipFilePath, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - if (Directory.Exists(entryPath)) - { - Directory.Delete(entryPath, recursive: true); - } - else - { - File.Delete(entryPath); - } - } - - var appRoot = Path.GetFullPath(userAppPath); - if (!appRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && - !appRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) - { - appRoot += Path.DirectorySeparatorChar; - } - - using (var archive = ZipFile.OpenRead(zipFilePath)) - { - foreach (var entry in archive.Entries) - { - var destinationPath = Path.Combine(userAppPath, entry.FullName); - var fullDest = Path.GetFullPath(destinationPath); - - // Guard against "zip slip" - a malicious/corrupt archive entry trying to - // extract outside of the mcUserApp folder via ../ path segments. - if (!fullDest.StartsWith(appRoot, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - } - - if (string.IsNullOrEmpty(entry.Name)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - var parentDir = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(parentDir)) - { - Directory.CreateDirectory(parentDir); - } - - entry.ExtractToFile(destinationPath, overwrite: true); - } - } - - File.Delete(zipFilePath); - - this.LogInformation("Successfully deployed mcUserApp from {zipFile}", Path.GetFileName(zipFilePath)); - } - catch (Exception ex) - { - this.LogError("Error deploying mcUserApp zip file {zipFile}: {message}", zipFilePath, ex.Message); - this.LogDebug(ex, "Stack Trace"); - } - } - private MobileControlApplicationConfig GetApplicationConfig(string processorIp) { try { var config = new MobileControlApplicationConfig { - ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port), + ApiPath = string.Format("{0}://{1}:{2}/mc/api", HttpScheme, processorIp, Port), GatewayAppPath = "", LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png", EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false, @@ -1021,7 +925,7 @@ namespace PepperDash.Essentials.WebSocketServer /// /// Removes all clients from the server /// - private void RemoveAllTokens(string s) + public void RemoveAllTokens(string s) { if (s == "?" || string.IsNullOrEmpty(s)) { @@ -1205,6 +1109,7 @@ namespace PepperDash.Essentials.WebSocketServer res.StatusCode = 200; res.Close(); + // remote log collector has no dedicated secure flag; keep it on http regardless of DirectServer.Secure var logRequest = new HttpRequestMessage(HttpMethod.Post, $"http://{_parent.Config.DirectServer.Logging.Host}:{_parent.Config.DirectServer.Logging.Port}/logs") { Content = new StringContent(body, Encoding.UTF8, "application/json"), @@ -1257,16 +1162,19 @@ namespace PepperDash.Essentials.WebSocketServer var qp = req.QueryString; var token = qp["token"]; + // Each join mints a single-use clientId; the panel webview must never replay a cached + // response, or it reconnects forever with an already-consumed id (1008 loop). + res.Headers.Add("Cache-Control", "no-store"); + res.Headers.Add("Pragma", "no-cache"); + this.LogVerbose("Join Room Request with token: {token}", token); byte[] body; - if (string.IsNullOrEmpty(token) || !UiClientContexts.TryGetValue(token, out UiClientContext clientContext)) + if (!UiClientContexts.TryGetValue(token, out UiClientContext clientContext)) { - var message = string.IsNullOrEmpty(token) - ? "Token is required" - : "Token invalid or has expired"; - res.StatusCode = string.IsNullOrEmpty(token) ? 400 : 401; + var message = "Token invalid or has expired"; + res.StatusCode = 401; res.ContentType = "application/json"; this.LogVerbose("{message}", message); body = Encoding.UTF8.GetBytes(message); @@ -1322,8 +1230,7 @@ namespace PepperDash.Essentials.WebSocketServer this.LogVerbose("Assigning ClientId: {clientId} for token: {token} at {timestamp}", clientId, token, now); // Construct WebSocket URL with clientId query parameter - var wsProtocol = "ws"; - var wsUrl = $"{wsProtocol}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; + var wsUrl = $"{WsScheme}://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{Port}{_wsPath}{token}?clientId={clientId}"; // Construct the response object JoinResponse jRes = new JoinResponse @@ -1335,7 +1242,8 @@ namespace PepperDash.Essentials.WebSocketServer Config = _parent.GetConfigWithPluginVersion(), CodeExpires = new DateTime().AddYears(1), UserCode = bridge.UserCode, - UserAppUrl = string.Format("http://{0}:{1}/mc/app/", + UserAppUrl = string.Format("{0}://{1}:{2}/mc/app", + HttpScheme, CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0), Port), WebSocketUrl = wsUrl, @@ -1360,6 +1268,8 @@ namespace PepperDash.Essentials.WebSocketServer { res.StatusCode = 200; res.ContentType = "application/json"; + res.Headers.Add("Cache-Control", "no-store"); + res.Headers.Add("Pragma", "no-cache"); var version = new Version() { ServerVersion = _parent.GetConfigWithPluginVersion().RuntimeInfo.PluginVersion }; var message = JsonConvert.SerializeObject(version); this.LogVerbose("{message}", message); @@ -1380,13 +1290,9 @@ namespace PepperDash.Essentials.WebSocketServer Debug.LogMessage(LogEventLevel.Verbose, "Requesting Image: {0}", this, path); - // Served from the program's own slot-specific folder (e.g. /user/program2/logo/) via - // the Mobile Control direct server, rather than the processor's CWS /html/ webserver. - var imageBasePath = Global.FilePathPrefix + "logo" + Global.DirectorySeparator; + var imageBasePath = Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo" + Global.DirectorySeparator; - // Path.GetFileName strips any directory components (including URL-decoded traversal - // sequences like "..") so only a bare filename within imageBasePath can ever be read. - var image = Path.GetFileName(Uri.UnescapeDataString(path.Split('?')[0])); + var image = path.Split('/').Last(); var filePath = imageBasePath + image; @@ -1412,16 +1318,7 @@ namespace PepperDash.Essentials.WebSocketServer } byte[] contents = File.ReadAllBytes(filePath); res.ContentLength64 = contents.LongLength; - try - { - res.Close(contents, true); - } - catch (IOException ex) - { - // Client disconnected (e.g. panel refreshed/navigated away) before the response could be - // fully written. This is a benign, expected condition, not an application error. - this.LogVerbose("Client disconnected before image response could be sent: {message}", ex.Message); - } + res.Close(contents, true); } else { @@ -1451,6 +1348,7 @@ namespace PepperDash.Essentials.WebSocketServer var suffix = filePath.Substring(_userAppBaseHref.Length, filePath.Length - _userAppBaseHref.Length); if (suffix != "/") { + //Debug.Console(2, this, "Suffix: {0}", suffix); filePath = filePath.Replace(suffix, ""); } } @@ -1474,48 +1372,27 @@ namespace PepperDash.Essentials.WebSocketServer } } - // Set ContentType based on file type. Use filePath so query params do not break extension checks. + // Set ContentType based on file type if (filePath.EndsWith(".html")) { this.LogVerbose("Client requesting User App"); res.ContentType = "text/html"; } - else if (filePath.EndsWith(".js")) + else { - res.ContentType = "application/javascript"; - } - else if (filePath.EndsWith(".css")) - { - res.ContentType = "text/css"; - } - else if (filePath.EndsWith(".json")) - { - res.ContentType = "application/json"; - } - else if (filePath.EndsWith(".svg")) - { - res.ContentType = "image/svg+xml"; - } - else if (filePath.EndsWith(".png")) - { - res.ContentType = "image/png"; - } - else if (filePath.EndsWith(".jpg") || filePath.EndsWith(".jpeg")) - { - res.ContentType = "image/jpeg"; - } - else if (filePath.EndsWith(".gif")) - { - res.ContentType = "image/gif"; - } - else if (filePath.EndsWith(".webp")) - { - res.ContentType = "image/webp"; - } - else if (filePath.EndsWith(".ico")) - { - res.ContentType = "image/x-icon"; + if (path.EndsWith(".js")) + { + res.ContentType = "application/javascript"; + } + else if (path.EndsWith(".css")) + { + res.ContentType = "text/css"; + } + else if (path.EndsWith(".json")) + { + res.ContentType = "application/json"; + } } this.LogVerbose("Attempting to serve file: {filePath}", filePath); @@ -1543,17 +1420,7 @@ namespace PepperDash.Essentials.WebSocketServer } res.ContentLength64 = contents.LongLength; - try - { - res.Close(contents, true); - } - catch (IOException ex) - { - // Client disconnected (e.g. panel refreshed/navigated away, or made a duplicate request) - // before the response could be fully written. This is a benign, expected condition - // (e.g. a "Broken pipe" IOException), not an application error worth logging at Error level. - this.LogVerbose("Client disconnected before user app response could be sent: {message}", ex.Message); - } + res.Close(contents, true); } /// @@ -1617,4 +1484,4 @@ namespace PepperDash.Essentials.WebSocketServer } } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs index 99e25634..607323ba 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/UiClient.cs @@ -267,4 +267,4 @@ namespace PepperDash.Essentials.WebSocketServer this.LogDebug(e.Exception, "Stack Trace"); } } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.MobileControl/WebSocketServer/WebSocketServerSecretProvider.cs b/src/PepperDash.Essentials.MobileControl/WebSocketServer/WebSocketServerSecretProvider.cs index 566ed22a..3dd67bd8 100644 --- a/src/PepperDash.Essentials.MobileControl/WebSocketServer/WebSocketServerSecretProvider.cs +++ b/src/PepperDash.Essentials.MobileControl/WebSocketServer/WebSocketServerSecretProvider.cs @@ -52,4 +52,4 @@ namespace PepperDash.Essentials.WebSocketServer } -} \ No newline at end of file +} diff --git a/src/PepperDash.Essentials.Tests/ControlSystem/LoadAssetsTests.cs b/src/PepperDash.Essentials.Tests/ControlSystem/LoadAssetsTests.cs deleted file mode 100644 index 0d06bcb8..00000000 --- a/src/PepperDash.Essentials.Tests/ControlSystem/LoadAssetsTests.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System.IO.Compression; -using FluentAssertions; -using Xunit; - -namespace PepperDash.Essentials.Tests.ControlSystem; - -/// -/// Tests for . -/// is the System.IO-based implementation that backs -/// . Tests run against a -/// temporary directory tree so no Crestron runtime is required. -/// Debug is initialised with fakes via TestInitializer. -/// -public sealed class LoadAssetsTests : IDisposable -{ - // --------------------------------------------------------------------------- - // Fixture: each test gets an isolated temp directory tree - // - // _rootDir/ - // appdir/ ← applicationDirectoryPath (where the loader scans) - // user/ - // program1/ ← filePathPrefix (where assets land) - // --------------------------------------------------------------------------- - - private readonly string _rootDir; - private readonly string _appDir; - private readonly string _filePathPrefix; - - public LoadAssetsTests() - { - _rootDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - _appDir = Path.Combine(_rootDir, "appdir"); - _filePathPrefix = Path.Combine(_rootDir, "user", "program1") + Path.DirectorySeparatorChar; - - Directory.CreateDirectory(_appDir); - Directory.CreateDirectory(_filePathPrefix); - } - - public void Dispose() - { - if (Directory.Exists(_rootDir)) - Directory.Delete(_rootDir, recursive: true); - } - - // --------------------------------------------------------------------------- - // Helpers - // --------------------------------------------------------------------------- - - private static byte[] EmptyZip() - { - using var ms = new MemoryStream(); - using (var _ = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) { } - return ms.ToArray(); - } - - private static byte[] ZipWithFile(string entryName, string content = "test") - { - using var ms = new MemoryStream(); - using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) - { - var entry = archive.CreateEntry(entryName); - using var sw = new StreamWriter(entry.Open()); - sw.Write(content); - } - return ms.ToArray(); - } - - private static byte[] ZipWithDirectory(string directoryEntryName) - { - // Directory entries have a trailing slash and an empty Name - var normalised = directoryEntryName.TrimEnd('/') + '/'; - using var ms = new MemoryStream(); - using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) - { - archive.CreateEntry(normalised); - } - return ms.ToArray(); - } - - private static byte[] ZipWithTraversalEntry() - { - using var ms = new MemoryStream(); - using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) - { - var entry = archive.CreateEntry("../traversal.txt"); - using var sw = new StreamWriter(entry.Open()); - sw.Write("should not appear"); - } - return ms.ToArray(); - } - - private void WriteToAppDir(string fileName, byte[] contents) => - File.WriteAllBytes(Path.Combine(_appDir, fileName), contents); - - private void WriteTextToAppDir(string fileName, string text) => - File.WriteAllText(Path.Combine(_appDir, fileName), text); - - // --------------------------------------------------------------------------- - // No-op cases — nothing in the application directory - // --------------------------------------------------------------------------- - - [Fact] - public void LoadAssets_EmptyApplicationDirectory_DoesNotThrow() - { - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().NotThrow(); - } - - // --------------------------------------------------------------------------- - // assets*.zip - // --------------------------------------------------------------------------- - - [Fact] - public void LoadAssets_MultipleAssetsZips_ThrowsException() - { - WriteToAppDir("assets1.zip", EmptyZip()); - WriteToAppDir("assets2.zip", EmptyZip()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw().WithMessage("*Multiple assets zip files*"); - } - - [Fact] - public void LoadAssets_SingleAssetsZip_ExtractsFileToFilePathPrefix() - { - WriteToAppDir("assets.zip", ZipWithFile("config.json", "{}")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.Exists(Path.Combine(_filePathPrefix, "config.json")).Should().BeTrue(); - } - - [Fact] - public void LoadAssets_SingleAssetsZip_FileContentsArePreserved() - { - const string expected = "{\"key\":\"value\"}"; - WriteToAppDir("assets.zip", ZipWithFile("data.json", expected)); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.ReadAllText(Path.Combine(_filePathPrefix, "data.json")).Should().Be(expected); - } - - [Fact] - public void LoadAssets_SingleAssetsZip_ZipIsDeletedAfterExtraction() - { - var zipPath = Path.Combine(_appDir, "assets.zip"); - WriteToAppDir("assets.zip", ZipWithFile("file.txt")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.Exists(zipPath).Should().BeFalse("assets zip should be cleaned up after extraction"); - } - - [Fact] - public void LoadAssets_AssetsZipWithDirectoryEntry_CreatesDirectory() - { - WriteToAppDir("assets.zip", ZipWithDirectory("subdir/")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - Directory.Exists(Path.Combine(_filePathPrefix, "subdir/")).Should().BeTrue(); - } - - [Fact] - public void LoadAssets_AssetsZipWithPathTraversal_ThrowsInvalidOperationException() - { - WriteToAppDir("assets.zip", ZipWithTraversalEntry()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw() - .WithMessage("*trying to extract outside of the target directory*"); - } - - // --------------------------------------------------------------------------- - // htmlassets*.zip - // --------------------------------------------------------------------------- - - [Fact] - public void LoadAssets_MultipleHtmlAssetsZips_ThrowsException() - { - WriteToAppDir("htmlassets1.zip", EmptyZip()); - WriteToAppDir("htmlassets2.zip", EmptyZip()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw().WithMessage("*Multiple htmlassets zip files*"); - } - - [Fact] - public void LoadAssets_SingleHtmlAssetsZip_ExtractsToHtmlDirectory() - { - // htmlDir = rootDir/html - WriteToAppDir("htmlassets.zip", ZipWithFile("index.html", "")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - var expectedHtmlDir = Path.Combine(_rootDir, "html"); - File.Exists(Path.Combine(expectedHtmlDir, "index.html")).Should().BeTrue(); - } - - [Fact] - public void LoadAssets_SingleHtmlAssetsZip_ZipIsDeletedAfterExtraction() - { - var zipPath = Path.Combine(_appDir, "htmlassets.zip"); - WriteToAppDir("htmlassets.zip", ZipWithFile("page.html")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.Exists(zipPath).Should().BeFalse(); - } - - [Fact] - public void LoadAssets_HtmlAssetsZipWithPathTraversal_ThrowsInvalidOperationException() - { - WriteToAppDir("htmlassets.zip", ZipWithTraversalEntry()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw() - .WithMessage("*trying to extract outside of the target directory*"); - } - - [Fact] - public void LoadAssets_HtmlAssetsZip_ShallowFilePathPrefixThrowsWhenRootCannotBeDetermined() - { - // A path that is exactly ONE level below the filesystem root has no grandparent, - // so rootDir (programDir.Parent.Parent) is null and the guard should throw. - // DirectoryInfo works with non-existent paths, so we don't create the directory. - var filesystemRoot = Path.GetPathRoot(Path.GetTempPath())!; - var shallowPrefix = Path.Combine(filesystemRoot, "pepperDashTestShallow") + Path.DirectorySeparatorChar; - WriteToAppDir("htmlassets.zip", ZipWithFile("page.html")); - - var act = () => AssetLoader.Load(_appDir, shallowPrefix); - act.Should().Throw().WithMessage("*Unable to determine root directory for html extraction*"); - } - - // --------------------------------------------------------------------------- - // essentials-devtools*.zip - // --------------------------------------------------------------------------- - - [Fact] - public void LoadAssets_MultipleDevToolsZips_ThrowsException() - { - WriteToAppDir("essentials-devtools1.zip", EmptyZip()); - WriteToAppDir("essentials-devtools2.zip", EmptyZip()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw().WithMessage("*Multiple essentials-devtools zip files*"); - } - - [Fact] - public void LoadAssets_SingleDevToolsZip_ExtractsToHtmlDebugDirectory() - { - // debugDir = rootDir/html/debug - WriteToAppDir("essentials-devtools.zip", ZipWithFile("app.js", "console.log('hi');")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - var expectedDebugDir = Path.Combine(_rootDir, "html", "debug"); - File.Exists(Path.Combine(expectedDebugDir, "app.js")).Should().BeTrue(); - } - - [Fact] - public void LoadAssets_SingleDevToolsZip_ZipIsDeletedAfterExtraction() - { - var zipPath = Path.Combine(_appDir, "essentials-devtools.zip"); - WriteToAppDir("essentials-devtools.zip", ZipWithFile("tool.js")); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.Exists(zipPath).Should().BeFalse(); - } - - [Fact] - public void LoadAssets_DevToolsZipWithPathTraversal_ThrowsInvalidOperationException() - { - WriteToAppDir("essentials-devtools.zip", ZipWithTraversalEntry()); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw() - .WithMessage("*trying to extract outside of the target directory*"); - } - - // --------------------------------------------------------------------------- - // *configurationFile*.json - // --------------------------------------------------------------------------- - - [Fact] - public void LoadAssets_MultipleJsonConfigFiles_ThrowsException() - { - WriteTextToAppDir("abcconfigurationFile1.json", "{}"); - WriteTextToAppDir("abcconfigurationFile2.json", "{}"); - - var act = () => AssetLoader.Load(_appDir, _filePathPrefix); - act.Should().Throw().WithMessage("*Multiple configuration files found*"); - } - - [Fact] - public void LoadAssets_SingleJsonConfigFile_IsMovedToFilePathPrefix() - { - const string fileName = "myconfigurationFile.json"; - WriteTextToAppDir(fileName, "{}"); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.Exists(Path.Combine(_appDir, fileName)).Should().BeFalse("source file should be moved, not copied"); - File.Exists(Path.Combine(_filePathPrefix, fileName)).Should().BeTrue("file should exist at the file path prefix"); - } - - [Fact] - public void LoadAssets_SingleJsonConfigFile_ExistingDestinationIsReplaced() - { - const string fileName = "myconfigurationFile.json"; - WriteTextToAppDir(fileName, "new content"); - - // Pre-populate the destination with stale content - File.WriteAllText(Path.Combine(_filePathPrefix, fileName), "old content"); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.ReadAllText(Path.Combine(_filePathPrefix, fileName)).Should().Be("new content"); - } - - [Fact] - public void LoadAssets_SingleJsonConfigFile_ContentIsPreserved() - { - const string content = "{\"devices\":[]}"; - const string fileName = "myconfigurationFile.json"; - WriteTextToAppDir(fileName, content); - - AssetLoader.Load(_appDir, _filePathPrefix); - - File.ReadAllText(Path.Combine(_filePathPrefix, fileName)).Should().Be(content); - } -} diff --git a/src/PepperDash.Essentials.Tests/Fakes/Fakes.cs b/src/PepperDash.Essentials.Tests/Fakes/Fakes.cs deleted file mode 100644 index ba195788..00000000 --- a/src/PepperDash.Essentials.Tests/Fakes/Fakes.cs +++ /dev/null @@ -1,61 +0,0 @@ -using PepperDash.Core.Abstractions; - -namespace PepperDash.Essentials.Tests.Fakes; - -internal class FakeCrestronEnvironment : ICrestronEnvironment -{ - public DevicePlatform DevicePlatform { get; set; } = DevicePlatform.Appliance; - public RuntimeEnvironment RuntimeEnvironment { get; set; } = RuntimeEnvironment.SimplSharpPro; - public string NewLine { get; set; } = "\r\n"; - public uint ApplicationNumber { get; set; } = 1; - public uint RoomId { get; set; } = 0; - - public event EventHandler? ProgramStatusChanged - { - add { } - remove { } - } - - public event EventHandler? EthernetEventReceived - { - add { } - remove { } - } - - public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath(); - public bool IsHardwareRuntime => false; -} - -internal class NoOpCrestronConsole : ICrestronConsole -{ - public void PrintLine(string message) { } - public void Print(string message) { } - public void ConsoleCommandResponse(string message) { } - public void AddNewConsoleCommand(Action _, string __, string ___, ConsoleAccessLevel ____) { } -} - -internal class InMemoryCrestronDataStore : ICrestronDataStore -{ - private readonly Dictionary _store = new(); - - public void InitStore() { } - - public bool TryGetLocalInt(string key, out int value) - { - if (_store.TryGetValue(key, out var raw) && raw is int i) { value = i; return true; } - value = 0; - return false; - } - - public bool SetLocalInt(string key, int value) { _store[key] = value; return true; } - public bool SetLocalUint(string key, uint value) { _store[key] = (int)value; return true; } - - public bool TryGetLocalBool(string key, out bool value) - { - if (_store.TryGetValue(key, out var raw) && raw is bool b) { value = b; return true; } - value = false; - return false; - } - - public bool SetLocalBool(string key, bool value) { _store[key] = value; return true; } -} diff --git a/src/PepperDash.Essentials.Tests/PepperDash.Essentials.Tests.csproj b/src/PepperDash.Essentials.Tests/PepperDash.Essentials.Tests.csproj deleted file mode 100644 index b32ff955..00000000 --- a/src/PepperDash.Essentials.Tests/PepperDash.Essentials.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - net9.0 - enable - enable - false - false - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/src/PepperDash.Essentials.Tests/TestInitializer.cs b/src/PepperDash.Essentials.Tests/TestInitializer.cs deleted file mode 100644 index acf5e271..00000000 --- a/src/PepperDash.Essentials.Tests/TestInitializer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.CompilerServices; -using PepperDash.Core.Abstractions; -using PepperDash.Essentials.Tests.Fakes; - -namespace PepperDash.Essentials.Tests; - -/// -/// Runs once before any type in this assembly is accessed. -/// Registers fake Crestron service implementations so that the Debug static -/// constructor never tries to reach the real Crestron SDK. -/// -internal static class TestInitializer -{ - [ModuleInitializer] - internal static void Initialize() - { - DebugServiceRegistration.Register( - new FakeCrestronEnvironment - { - DevicePlatform = DevicePlatform.Server, - RuntimeEnvironment = RuntimeEnvironment.Other, - }, - new NoOpCrestronConsole(), - new InMemoryCrestronDataStore()); - } -} - - diff --git a/src/PepperDash.Essentials/AssetLoader.cs b/src/PepperDash.Essentials/AssetLoader.cs index 9db98c7e..d8b9f5c1 100644 --- a/src/PepperDash.Essentials/AssetLoader.cs +++ b/src/PepperDash.Essentials/AssetLoader.cs @@ -5,252 +5,253 @@ using System.Linq; using PepperDash.Core; using Serilog.Events; -namespace PepperDash.Essentials; - -/// -/// Handles extracting embedded asset bundles and moving configuration files from the -/// application directory to the program file-path prefix at startup. -/// Implemented using System.IO types so it can run (and be tested) outside -/// of a Crestron runtime environment. -/// -internal static class AssetLoader +namespace PepperDash.Essentials { /// - /// Scans for well-known zip bundles and - /// JSON configuration files and deploys them to . + /// Handles extracting embedded asset bundles and moving configuration files from the + /// application directory to the program file-path prefix at startup. + /// Implemented using System.IO types so it can run (and be tested) outside + /// of a Crestron runtime environment. /// - /// - /// The directory to scan (typically the Crestron application root). - /// - /// - /// The program's runtime working directory (e.g. /nvram/program1/). - /// - internal static void Load(string applicationDirectoryPath, string filePathPrefix) + internal static class AssetLoader { - var applicationDirectory = new DirectoryInfo(applicationDirectoryPath); - Debug.LogMessage(LogEventLevel.Information, - "Searching: {applicationDirectory:l} for embedded assets - {Destination}", - applicationDirectory.FullName, filePathPrefix); - - ExtractAssetsZip(applicationDirectory, filePathPrefix); - ExtractHtmlAssetsZip(applicationDirectory, filePathPrefix); - ExtractDevToolsZip(applicationDirectory, filePathPrefix); - MoveConfigurationFile(applicationDirectory, filePathPrefix); - } - - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- - - private static void ExtractAssetsZip(DirectoryInfo applicationDirectory, string filePathPrefix) - { - var zipFiles = applicationDirectory.GetFiles("assets*.zip"); - - if (zipFiles.Length > 1) - throw new Exception("Multiple assets zip files found. Cannot continue."); - - if (zipFiles.Length == 1) + /// + /// Scans for well-known zip bundles and + /// JSON configuration files and deploys them to . + /// + /// + /// The directory to scan (typically the Crestron application root). + /// + /// + /// The program's runtime working directory (e.g. /nvram/program1/). + /// + internal static void Load(string applicationDirectoryPath, string filePathPrefix) { - var zipFile = zipFiles[0]; - var assetsRoot = Path.GetFullPath(filePathPrefix); - if (!assetsRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && - !assetsRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) - { - assetsRoot += Path.DirectorySeparatorChar; - } - + var applicationDirectory = new DirectoryInfo(applicationDirectoryPath); Debug.LogMessage(LogEventLevel.Information, - "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName); + "Searching: {applicationDirectory:l} for embedded assets - {Destination}", + applicationDirectory.FullName, filePathPrefix); - using (var archive = ZipFile.OpenRead(zipFile.FullName)) + ExtractAssetsZip(applicationDirectory, filePathPrefix); + ExtractHtmlAssetsZip(applicationDirectory, filePathPrefix); + ExtractDevToolsZip(applicationDirectory, filePathPrefix); + MoveConfigurationFile(applicationDirectory, filePathPrefix); + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + private static void ExtractAssetsZip(DirectoryInfo applicationDirectory, string filePathPrefix) + { + var zipFiles = applicationDirectory.GetFiles("assets*.zip"); + + if (zipFiles.Length > 1) + throw new Exception("Multiple assets zip files found. Cannot continue."); + + if (zipFiles.Length == 1) { - foreach (var entry in archive.Entries) + var zipFile = zipFiles[0]; + var assetsRoot = Path.GetFullPath(filePathPrefix); + if (!assetsRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && + !assetsRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) { - var destinationPath = Path.Combine(filePathPrefix, entry.FullName); - var fullDest = Path.GetFullPath(destinationPath); - if (!fullDest.StartsWith(assetsRoot, StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException( - $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - - if (string.IsNullOrEmpty(entry.Name)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - if (Directory.Exists(destinationPath)) - Directory.Delete(destinationPath, recursive: true); - - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); - entry.ExtractToFile(destinationPath, overwrite: true); - Debug.LogMessage(LogEventLevel.Information, - "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + assetsRoot += Path.DirectorySeparatorChar; } - } - } - foreach (var file in zipFiles) - File.Delete(file.FullName); - } - - private static void ExtractHtmlAssetsZip(DirectoryInfo applicationDirectory, string filePathPrefix) - { - var htmlZipFiles = applicationDirectory.GetFiles("htmlassets*.zip"); - - if (htmlZipFiles.Length > 1) - throw new Exception( - "Multiple htmlassets zip files found in application directory. " + - "Please ensure only one htmlassets*.zip file is present and retry."); - - if (htmlZipFiles.Length == 1) - { - var htmlZipFile = htmlZipFiles[0]; - var programDir = new DirectoryInfo( - filePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); - var userOrNvramDir = programDir.Parent; - var rootDir = userOrNvramDir?.Parent; - if (rootDir == null) - throw new Exception( - $"Unable to determine root directory for html extraction. Current path: {filePathPrefix}"); - - var htmlDir = Path.Combine(rootDir.FullName, "html"); - var htmlRoot = Path.GetFullPath(htmlDir); - if (!htmlRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && - !htmlRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) - { - htmlRoot += Path.DirectorySeparatorChar; - } - - Debug.LogMessage(LogEventLevel.Information, - "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName); - - using (var archive = ZipFile.OpenRead(htmlZipFile.FullName)) - { - foreach (var entry in archive.Entries) - { - var destinationPath = Path.Combine(htmlDir, entry.FullName); - var fullDest = Path.GetFullPath(destinationPath); - if (!fullDest.StartsWith(htmlRoot, StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException( - $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - - if (string.IsNullOrEmpty(entry.Name)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - if (File.Exists(destinationPath)) - File.Delete(destinationPath); - - var parentDir = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(parentDir)) - Directory.CreateDirectory(parentDir); - - entry.ExtractToFile(destinationPath, overwrite: true); - Debug.LogMessage(LogEventLevel.Information, - "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); - } - } - } - - foreach (var file in htmlZipFiles) - File.Delete(file.FullName); - } - - private static void ExtractDevToolsZip(DirectoryInfo applicationDirectory, string filePathPrefix) - { - var devToolsZipFiles = applicationDirectory.GetFiles("essentials-devtools*.zip"); - - if (devToolsZipFiles.Length > 1) - throw new Exception( - "Multiple essentials-devtools zip files found in application directory. " + - "Please ensure only one essentials-devtools*.zip file is present and retry."); - - if (devToolsZipFiles.Length == 1) - { - var devToolsZipFile = devToolsZipFiles[0]; - var programDir = new DirectoryInfo( - filePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); - var userOrNvramDir = programDir.Parent; - var rootDir = userOrNvramDir?.Parent; - if (rootDir == null) - throw new Exception( - $"Unable to determine root directory for debug html extraction. Current path: {filePathPrefix}"); - - var debugDir = Path.Combine(rootDir.FullName, "html", "debug"); - var debugRoot = Path.GetFullPath(debugDir); - if (!debugRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && - !debugRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) - { - debugRoot += Path.DirectorySeparatorChar; - } - - Debug.LogMessage(LogEventLevel.Information, - "Found essentials-devtools zip file: {zipFile:l}... Unzipping to {Destination}...", - devToolsZipFile.FullName, debugDir); - - using (var archive = ZipFile.OpenRead(devToolsZipFile.FullName)) - { - foreach (var entry in archive.Entries) - { - var destinationPath = Path.Combine(debugDir, entry.FullName); - var fullDest = Path.GetFullPath(destinationPath); - if (!fullDest.StartsWith(debugRoot, StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException( - $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); - - if (string.IsNullOrEmpty(entry.Name)) - { - Directory.CreateDirectory(destinationPath); - continue; - } - - if (File.Exists(destinationPath)) - File.Delete(destinationPath); - - var parentDir = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrEmpty(parentDir)) - Directory.CreateDirectory(parentDir); - - entry.ExtractToFile(destinationPath, overwrite: true); - Debug.LogMessage(LogEventLevel.Information, - "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); - } - } - } - - foreach (var file in devToolsZipFiles) - File.Delete(file.FullName); - } - - private static void MoveConfigurationFile(DirectoryInfo applicationDirectory, string filePathPrefix) - { - var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json"); - - if (jsonFiles.Length > 1) - { - Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", - jsonFiles.Select(f => f.FullName).ToArray()); - throw new Exception("Multiple configuration files found. Cannot continue."); - } - - if (jsonFiles.Length == 1) - { - var jsonFile = jsonFiles[0]; - var finalPath = Path.Combine(filePathPrefix, jsonFile.Name); - Debug.LogMessage(LogEventLevel.Information, - "Found configuration file: {jsonFile:l}... Moving to: {Destination}", - jsonFile.FullName, finalPath); - - if (File.Exists(finalPath)) - { Debug.LogMessage(LogEventLevel.Information, - "Removing existing configuration file: {Destination}", finalPath); - File.Delete(finalPath); + "Found assets zip file: {zipFile:l}... Unzipping...", zipFile.FullName); + + using (var archive = ZipFile.OpenRead(zipFile.FullName)) + { + foreach (var entry in archive.Entries) + { + var destinationPath = Path.Combine(filePathPrefix, entry.FullName); + var fullDest = Path.GetFullPath(destinationPath); + if (!fullDest.StartsWith(assetsRoot, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException( + $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); + + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + if (Directory.Exists(destinationPath)) + Directory.Delete(destinationPath, recursive: true); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + entry.ExtractToFile(destinationPath, overwrite: true); + Debug.LogMessage(LogEventLevel.Information, + "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + } + } } - jsonFile.MoveTo(finalPath); + foreach (var file in zipFiles) + File.Delete(file.FullName); + } + + private static void ExtractHtmlAssetsZip(DirectoryInfo applicationDirectory, string filePathPrefix) + { + var htmlZipFiles = applicationDirectory.GetFiles("htmlassets*.zip"); + + if (htmlZipFiles.Length > 1) + throw new Exception( + "Multiple htmlassets zip files found in application directory. " + + "Please ensure only one htmlassets*.zip file is present and retry."); + + if (htmlZipFiles.Length == 1) + { + var htmlZipFile = htmlZipFiles[0]; + var programDir = new DirectoryInfo( + filePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); + var userOrNvramDir = programDir.Parent; + var rootDir = userOrNvramDir?.Parent; + if (rootDir == null) + throw new Exception( + $"Unable to determine root directory for html extraction. Current path: {filePathPrefix}"); + + var htmlDir = Path.Combine(rootDir.FullName, "html"); + var htmlRoot = Path.GetFullPath(htmlDir); + if (!htmlRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && + !htmlRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + { + htmlRoot += Path.DirectorySeparatorChar; + } + + Debug.LogMessage(LogEventLevel.Information, + "Found htmlassets zip file: {zipFile:l}... Unzipping...", htmlZipFile.FullName); + + using (var archive = ZipFile.OpenRead(htmlZipFile.FullName)) + { + foreach (var entry in archive.Entries) + { + var destinationPath = Path.Combine(htmlDir, entry.FullName); + var fullDest = Path.GetFullPath(destinationPath); + if (!fullDest.StartsWith(htmlRoot, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException( + $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); + + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + if (File.Exists(destinationPath)) + File.Delete(destinationPath); + + var parentDir = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(parentDir)) + Directory.CreateDirectory(parentDir); + + entry.ExtractToFile(destinationPath, overwrite: true); + Debug.LogMessage(LogEventLevel.Information, + "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + } + } + } + + foreach (var file in htmlZipFiles) + File.Delete(file.FullName); + } + + private static void ExtractDevToolsZip(DirectoryInfo applicationDirectory, string filePathPrefix) + { + var devToolsZipFiles = applicationDirectory.GetFiles("essentials-devtools*.zip"); + + if (devToolsZipFiles.Length > 1) + throw new Exception( + "Multiple essentials-devtools zip files found in application directory. " + + "Please ensure only one essentials-devtools*.zip file is present and retry."); + + if (devToolsZipFiles.Length == 1) + { + var devToolsZipFile = devToolsZipFiles[0]; + var programDir = new DirectoryInfo( + filePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); + var userOrNvramDir = programDir.Parent; + var rootDir = userOrNvramDir?.Parent; + if (rootDir == null) + throw new Exception( + $"Unable to determine root directory for debug html extraction. Current path: {filePathPrefix}"); + + var debugDir = Path.Combine(rootDir.FullName, "html", "debug"); + var debugRoot = Path.GetFullPath(debugDir); + if (!debugRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) && + !debugRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString())) + { + debugRoot += Path.DirectorySeparatorChar; + } + + Debug.LogMessage(LogEventLevel.Information, + "Found essentials-devtools zip file: {zipFile:l}... Unzipping to {Destination}...", + devToolsZipFile.FullName, debugDir); + + using (var archive = ZipFile.OpenRead(devToolsZipFile.FullName)) + { + foreach (var entry in archive.Entries) + { + var destinationPath = Path.Combine(debugDir, entry.FullName); + var fullDest = Path.GetFullPath(destinationPath); + if (!fullDest.StartsWith(debugRoot, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException( + $"Entry '{entry.FullName}' is trying to extract outside of the target directory."); + + if (string.IsNullOrEmpty(entry.Name)) + { + Directory.CreateDirectory(destinationPath); + continue; + } + + if (File.Exists(destinationPath)) + File.Delete(destinationPath); + + var parentDir = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(parentDir)) + Directory.CreateDirectory(parentDir); + + entry.ExtractToFile(destinationPath, overwrite: true); + Debug.LogMessage(LogEventLevel.Information, + "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath); + } + } + } + + foreach (var file in devToolsZipFiles) + File.Delete(file.FullName); + } + + private static void MoveConfigurationFile(DirectoryInfo applicationDirectory, string filePathPrefix) + { + var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json"); + + if (jsonFiles.Length > 1) + { + Debug.LogError("Multiple configuration files found in application directory: {@jsonFiles}", + jsonFiles.Select(f => f.FullName).ToArray()); + throw new Exception("Multiple configuration files found. Cannot continue."); + } + + if (jsonFiles.Length == 1) + { + var jsonFile = jsonFiles[0]; + var finalPath = Path.Combine(filePathPrefix, jsonFile.Name); + Debug.LogMessage(LogEventLevel.Information, + "Found configuration file: {jsonFile:l}... Moving to: {Destination}", + jsonFile.FullName, finalPath); + + if (File.Exists(finalPath)) + { + Debug.LogMessage(LogEventLevel.Information, + "Removing existing configuration file: {Destination}", finalPath); + File.Delete(finalPath); + } + + jsonFile.MoveTo(finalPath); + } } } -} +} \ No newline at end of file diff --git a/src/PepperDash.Essentials/ClassDiagram1.cd b/src/PepperDash.Essentials/ClassDiagram1.cd new file mode 100644 index 00000000..aa0f4ed0 --- /dev/null +++ b/src/PepperDash.Essentials/ClassDiagram1.cd @@ -0,0 +1,881 @@ + + + + + + AAAAAQAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAIAgA= + Audio\EssentialsVolumeLevelConfig.cs + + + + + + AAAAAAAAEAAAAAAAAAAAAAAAAIAAAAgBAAAAAAAAAAA= + Audio\EssentialsVolumeLevelConfig.cs + + + + + + AAAAAAAIAAAAAAAABAAAACAAIAAAAQAAAAAAAAAAEAA= + Config\ConfigReader.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA= + Config\DeviceFactory.cs + + + + + + AAAAAAEAAAAAQAIAAAAAAAACAAAAAAAAAAAAAAEAAAA= + Config\EssentialsConfig.cs + + + + + + AAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Config\EssentialsConfig.cs + + + + + + AAAAAAAgEAAAAAAEAAAAAAAAAAAAAAAAAAAAAEAIAIA= + Configuration ORIGINAL\ConfigTieLine.cs + + + + + + AEAAAAAAAAAAgQAAAAAAgAAAAAAAAAAAAAAgEDAADAQ= + Configuration ORIGINAL\Configuration.cs + + + + + + AAAAAAAAAAAAAAQAAAAAAAQAAACAAAAAAAAAAAAAAAA= + Configuration ORIGINAL\Configuration.cs + + + + + + AAAAAAAAAgAAAAAAAEAAAAAAAAAAAAAAAAAgAAAAAAA= + Configuration ORIGINAL\Configuration.cs + + + + + + IQAAAAAEAAAAAAADAAACABQAAAAAAAAAAAAAAAAAAAA= + Configuration ORIGINAL\Configuration.cs + + + + + + AAAAAAAAAAAAAAAAAEAAAAAAAAAAAIAAAAAgAAAIAAA= + Configuration ORIGINAL\ConfigurationHelpers.cs + + + + + + AAAAAAAAAAAQAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA= + Configuration ORIGINAL\Factories\DmFactory.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAEDAAKAA= + Configuration ORIGINAL\Factories\FactoryHelper.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAABAAAA= + Configuration ORIGINAL\Factories\FactoryHelper.cs + + + + + + AAoAgAAIAEAACAAEAIEIBICAAAAAAABAAAAAAAAAAAA= + ControlSystem.cs + + + + + + AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA= + Devices\Amplifier.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAA= + Room\Cotija\CotijaConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAA= + Room\Cotija\CotijaConfig.cs + + + + + + CAAABAABAgACCAKGBIAAEyBAFAAACYSAgIAAAAJkAAA= + Room\Cotija\CotijaSystemController.cs + + + + + + AAQIQAAAAAAAAgAAAAAAQAAAAAAAAAAAAAAAABAAAAA= + Room\Cotija\RoomBridges\CotijaBridgeBase.cs + + + + + + AAAIAAAAIABAAkgAABgIAAAAAAAAAEAAAAAAgBAAACA= + Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA= + Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs + + + + + + iQFBQAIAAgAAgQAEQACAMABAAABAACAQUAAQAAgCgBA= + Room\Types\EssentialsHuddleSpaceRoom.cs + + + + + + + iQFBQAIAIgQMgQAEQAigMABAAABAADIwUACQAAgCgTE= + Room\Types\EssentialsHuddleVtc1Room.cs + + + + + + + iQAEAAIACkAAAQAOQASgMAAJAABgAAAQQAAQAAgCgBA= + Room\Types\EssentialsPresentationRoom.cs + + + + + + + gQAAEAICECgCAQAEAAAUIwIyAAAAgACiAgAAAQECAgA= + Room\Types\EssentialsRoomBase.cs + + + + + + AICCIAAIGIAAAAAgCAAIAEAAAAAAAAAAAAEAIAAAAAA= + UI\CrestronTouchpanelPropertiesConfig.cs + + + + + + AAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + UI\CrestronTouchpanelPropertiesConfig.cs + + + + + + AAAIAEAAAAAAAAAAAAAAAAEAAAAACKIBAAgACAAAAAA= + UI\EssentialsTouchpanelController.cs + + + + + + AAAAQAAAACAAAAABAAAAAAAAAABAAAAAAAAAAkAAAAA= + UI\HttpLogoServer.cs + + + + + + j+jWCNqEIGzi4UTaTgyn37kpncQJK7L42VMLmMgTE5A= + UI\JoinConstants\UIBoolJoin.cs + + + + + + BAggBIABCQAAGAAQAAAACAACAAAAAAAAAAIAAAAAAAA= + UI\JoinConstants\UISmartObjectJoin.cs + + + + + + BkBgIAgAAggOQAFGAYQIABACgCEBjkSQAUEAASIABCE= + UI\JoinConstants\UIStringlJoin.cs + + + + + + AAQAAAACAAAACAEAAIAAAAAAIABAIAAAABAAAEAAAAA= + UI\JoinConstants\UIUshortJoin.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA= + UI\SubpageReferenceListActivityItem.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA= + UI\SubpageReferenceListCallStagingItem.cs + + + + + + AAAABAAAAAAAAAAAAAAAAAAAAgAAAAACAAAAAABgAAA= + UI\SubpageReferenceListSourceItem.cs + + + + + + CAAIkAAAAAAQEAAAEAACAAAAAIAEABAAAgAACAAAAAA= + UIDrivers\enums and base.cs + + + + + + ABAAAAAAAhgAAAAAEAAAAAAAAIAEAAAAAAAAAAAAAAA= + UIDrivers\Essentials\EssentialsPanelMainInterfaceDriver.cs + + + + + + gTEAIIAiCggFNCQ4EA4AWBkAKJCEqAAOS4CKMAQQJQA= + UIDrivers\Essentials\EssentialsPresentationPanelAvFunctionsDriver.cs + + + + + + gRQAIICuAghENHQpEA4IWCkBMJDEsEAEC4CAMARQIBA= + UIDrivers\EssentialsHuddle\EssentialsHuddlePanelAvFunctionsDriver.cs + + + + + + GRwAIYC+oghAeHStEDAIWCdBMADEsBAcDwCAMARYIBg= + UIDrivers\EssentialsHuddleVTC\EssentialsHuddleVtc1PanelAvFunctionsDriver.cs + + + + + + + AAAQgAIAAAAAAAAAEAAAAAQAAAAEAAAAAAAAEAAAgAA= + UIDrivers\JoinedSigInterlock.cs + + + + + + AAAAAAAAAAAAAAAAEAAAQAAABIAEAAAAAAAAAAAAAAA= + UIDrivers\Page Drivers\SingleSubpageModalAndBackDriver.cs + + + + + + AAAAAAAAAAAAAAAAEAAAAAAABAAEAAAAAAAAAAAAAAA= + UIDrivers\Page Drivers\SingleSubpageModalDriver.cs + + + + + + AAAQAAIAAAAAAAAIEAAAAAAAAAAEAAAAAAAAEAAAgAA= + UIDrivers\SigInterlock.cs + + + + + + AAAAAAAACAAACBAAAAAAAAAAAAAAAAAAAAIAAAAAAQA= + UIDrivers\SmartObjectRoomsList.cs + + + + + + AAAAQAAAAAAAAAAAABAAQAAAAAAAAAACAAEAAAAAAAA= + UIDrivers\SmartObjectRoomsList.cs + + + + + + AAAAAAABAAAAAEAAAAAAAAAAAAAAAAAAAQAAAAAAAAA= + UIDrivers\VolumeAndSourceChangeArgs.cs + + + + + + IAGigRBpCgZwAIMSBBIbIgAAImAPtEBiAAgECpJgKQo= + OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs + + + + + + + AAAAAACgAIQAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAA= + OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAAAAAAAAAA= + OTHER\Fusion\EssentialsHuddleSpaceFusionSystemControllerBase.cs + + + + + + AACAAAAAAgAAAIAAAAAAAAAgIBAQAAAAAAAAAAAAAQA= + OTHER\Fusion\EssentialsHuddleVtc1FusionController.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionEventHandlers.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAA= + OTHER\Fusion\FusionEventHandlers.cs + + + + + + AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionProcessorQueries.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAEAAAA= + OTHER\Fusion\FusionProcessorQueries.cs + + + + + + AAICAAAAAAAAAAAAAAAAAACAAAATQAAAAAAAABAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAQAAAAAAQAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAQAAAAAAQAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAACACAAAAQAAAAAAAAAAAAAAAAAAAACAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAACAAAgAAQAAAAAAAAAAAAAAAAAAIAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAgAAQAAAAAAAAAAAAAAAAAAIAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAACAAAAAAQAAAAAAAAAAgAAAAAAAAAAABAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AABCAAAAYEAAEBIBAIAAJAQAQKAYAAAIEAAAEAACCgg= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAEAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAABAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAgAQAAIAAAAAAACAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA= + OTHER\Fusion\FusionRviDataClasses.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAIAgAAQAAAAAAAAEAAAAAAA= + Room\Config\DDVC01RoomPropertiesConfig.cs + + + + + + AAAAAAAAAAAAAAAAAEAAAAQAAAAAAAAAAAAAAAAAAAA= + Room\Config\DDVC01RoomPropertiesConfig.cs + + + + + + AAAAAAAAAAAAgAAAAAAAAAAACAAAAAABAAAAAAAAABA= + Room\Config\EssentialsHuddleRoomPropertiesConfig.cs + + + + + + AAAAAAAAAAAAgAAAAAAAAAAACIAAAAABAAAAAAAAABA= + Room\Config\EssentialsHuddleVtc1PropertiesConfig.cs + + + + + + AAAAAAACAIAAAAAAAAAAABAACAAAAAABAAAAAAAAABA= + Room\Config\EssentialsPresentationPropertiesConfig.cs + + + + + + AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAEAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AABAEQAAAAEoAAEEAAQAAAACAAAAAAgggAAAAQAAAgA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAEAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAgABAAAAAIAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACQAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AACAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAA= + Room\Config\EssentialsRoomConfig.cs + + + + + + AAAAAAAAAAAAABAACAAAAAAAAAAAAAAAAAAAAAAAAAA= + Room\Config\EssentialsRoomEmergencyConfig.cs + + + + + + AAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAABQAAAAAAAAA= + Room\Config\EssentialsRoomEmergencyConfig.cs + + + + + + AUAAAAgAAAACwAYAAAAAAAEAAAAAAAAAIcAAAEAAAAA= + Room\Cotija\CotijaDdvc01DeviceBridge.cs + + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\IChannelExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\IColorExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\IDPadExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\IDvrExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\INumericExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\IPowerExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\ISetTopBoxControlsExtensions.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= + Room\Cotija\DeviceTypeInterfaces\ITransportExtensions.cs + + + + + + gACIAAAAAAICAFAAAACAAAEIIAAAAAQAQAAAABAAAAA= + Room\Cotija\RoomBridges\CotijaDdvc01RoomBridge.cs + + + + + + + AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Room\Emergency\EsentialsRoomEmergencyContactClosure.cs + + + + + + + AAAAAAAAAAAAAAAASBAAAAAAAAAAAAAABAAAAAAAEAA= + Room\Emergency\EsentialsRoomEmergencyContactClosure.cs + + + + + + AAAQAAAAAiACDAAAGCAACgAIAABECBAgQAAAAQAAAAA= + UIDrivers\EssentialsHuddle\EssentialsHuddleTechPageDriver.cs + + + + + + XAASAoAiAagwAcBAGAUURWQEOHQFAKCmAABCNSSEDPA= + UIDrivers\VC\EssentialsVideoCodecUiDriver.cs + + + + + + AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Room\Types\EssentialsRoomBase.cs + + + + + + AAgAAAAAAAAACBAAACAAAAIBAAAEAAAEAgAAAAAAIAA= + UIDrivers\EssentialsHuddleVTC\EssentialsHuddleVtc1PanelAvFunctionsDriver.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA= + Room\Cotija\Interfaces.cs + + + + + + AAAAAAAAQAAAAAAACAAAAAAAAAAAAAAAAAAAAAEACAA= + Room\Types\EssentialsRoomBase.cs + + + + + + AAAAAAAAACAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAA= + Room\Types\EssentialsRoomBase.cs + + + + + + AAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAIEAAAA= + Room\Types\EssentialsRoomBase.cs + + + + + + AIAAAAAAAAAAAAAAAhBAAAAAAAAAAACYAAAAAAAAAAA= + UIDrivers\enums and base.cs + + + + + + AAAAAAAAAAAAABAAAAAAAAAAAAAAACAAAEAAAAAAAAA= + UIDrivers\enums and base.cs + + + + + + AAAAAAAEAAAAAAAAAIAAAAAAAAQAAAAQAAAAAAAAAAA= + UIDrivers\enums and base.cs + + + + + + AAAAABAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAA= + UIDrivers\enums and base.cs + + + + + + AAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + UIDrivers\enums and base.cs + + + + + + AAAAgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + UIDrivers\VolumeAndSourceChangeArgs.cs + + + + + + AAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Room\Cotija\RoomBridges\CotijaEssentialsHuddleSpaceRoomBridge.cs + + + + + + AAAAAAAAAAAAACAAACAAAAAAAAAAAAAAAAAAACAAAAA= + UIDrivers\VolumeAndSourceChangeArgs.cs + + + + \ No newline at end of file diff --git a/src/PepperDash.Essentials/ControlSystem.cs b/src/PepperDash.Essentials/ControlSystem.cs index 701a0ec4..47de4d3e 100644 --- a/src/PepperDash.Essentials/ControlSystem.cs +++ b/src/PepperDash.Essentials/ControlSystem.cs @@ -1,143 +1,102 @@ using System; -using System.Collections.Generic; -using System.IO.Compression; using System.Linq; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharpPro; +using Crestron.SimplSharpPro.CrestronThread; using Crestron.SimplSharpPro.Diagnostics; -using Crestron.SimplSharpPro.UC; using PepperDash.Core; -using PepperDash.Core.Abstractions; -using PepperDash.Core.Adapters; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Config; -using PepperDash.Essentials.Core.DeviceTypeInterfaces; using PepperDash.Essentials.Core.Routing; using PepperDash.Essentials.Core.Web; using Serilog.Events; -using Timeout = Crestron.SimplSharp.Timeout; -namespace PepperDash.Essentials; - -/// -/// Represents the main control system for the application, providing initialization, configuration loading, and device -/// management functionality. -/// -/// This class extends and serves as the entry point for the control -/// system. It manages the initialization of devices, rooms, tie lines, and other system components. Additionally, it -/// provides methods for platform determination, configuration loading, and system teardown. -public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitializationExceptions +namespace PepperDash.Essentials { - private Timer startTimer; - private ManualResetEventSlim initializeEvent; - private const long StartupTime = 500; - /// - /// List of exceptions that occurred during initialization. - /// This can be used to report issues with loading devices or tie lines without crashing the entire system, - /// which allows for partial functionality in cases where some components are misconfigured or have issues. + /// Main control system class that inherits from CrestronControlSystem and manages program lifecycle /// - public List InitializationExceptions { get; private set; } = new List(); - - /// - /// Initializes a new instance of the class, setting up the system's global state and - /// dependencies. - /// - /// This constructor configures the control system by initializing key components such as the - /// device manager and secrets manager, and sets global properties like the maximum number of user threads and the - /// program initialization state. It also adjusts the error log's minimum debug level based on the device - /// platform. - public ControlSystem() - : base() - + public class ControlSystem : CrestronControlSystem, ILoadConfig { - try + HttpLogoServer LogoServer; + + private CTimer _startTimer; + private CEvent _initializeEvent; + private const long StartupTime = 500; + + // private const string minimumFirmwareVersion = "2.8006.00110"; + + /// + /// Initializes a new instance of the ControlSystem class + /// + public ControlSystem() + : base() { - // Register Crestron service adapters BEFORE the first reference to Debug, - // so that Debug's static constructor uses these implementations instead of - // calling the Crestron SDK statics directly. - DebugServiceRegistration.Register( - new CrestronEnvironmentAdapter(), - new CrestronConsoleAdapter(), - new CrestronDataStoreAdapter()); - - Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400; - + Thread.MaxNumberOfUserThreads = 400; Global.ControlSystem = this; DeviceManager.Initialize(this); SecretsManager.Initialize(); SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; - - Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose); } - catch (Exception e) + + /// + /// InitializeSystem method + /// + /// + public override void InitializeSystem() { - try + + // Get FW version and stop if it's too low to run this version of Essentials. Must be greater than v2.8006.00110 + // var fwVersion = InitialParametersClass.FirmwareVersion; + + // Debug.LogInformation("Control System Hardware Version: {fwVersion}", fwVersion); + + // // split the version into parts and compare against minimumFirmwareVersion + // var versionParts = fwVersion.Split('.').Select(int.Parse).ToArray(); + // var minParts = minimumFirmwareVersion.Split('.').Select(int.Parse).ToArray(); + // if (versionParts.Length < minParts.Length + // || versionParts[0] < minParts[0] + // || (versionParts[0] == minParts[0] && versionParts[1] < minParts[1]) + // || (versionParts[0] == minParts[0] && versionParts[1] == minParts[1] && versionParts[2] <= minParts[2])) + // { + // Debug.LogFatal("Firmware version {fwVersion} is too low to run this version of Essentials. Please upgrade to greater than v{minimumFirmwareVersion}.", fwVersion, minimumFirmwareVersion); + // return; + // } + + // If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate + // to allow any HD-BaseT DM endpoints to register first. + bool preventInitializationComplete = Global.ControlSystemIsDmpsType; + if (preventInitializationComplete) { - Debug.LogError(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); + Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Entering **********************"); + + _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); + _initializeEvent = new CEvent(true, false); + DeviceManager.AllDevicesRegistered += (o, a) => + { + _initializeEvent.Set(); + }; + _initializeEvent.Wait(30000); + Debug.LogMessage(LogEventLevel.Debug, "******************* InitializeSystem() Exiting **********************"); + + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } - catch + else { - // Debug may not be initialized (e.g. its own static ctor failed); fall back to console. - CrestronConsole.PrintLine($"FATAL INITIALIZE ERROR. System is in an inconsistent state\r\n{e.Message}\r\n{e.StackTrace}"); + _startTimer = new CTimer(StartSystem, preventInitializationComplete, StartupTime); } } - } - /// - /// Initializes the control system and prepares it for operation. - /// - /// This method ensures that all devices in the system are properly registered and initialized - /// before the system is fully operational. If the control system is of a DMPS type, the method waits for all - /// devices to activate, allowing HD-BaseT DM endpoints to register before completing initialization. For non-DMPS - /// systems, initialization proceeds without waiting. - public override void InitializeSystem() - { - // If the control system is a DMPS type, we need to wait to exit this method until all devices have had time to activate - // to allow any HD-BaseT DM endpoints to register first. - bool preventInitializationComplete = Global.ControlSystemIsDmpsType; - if (preventInitializationComplete) - { - Debug.LogMessage(LogEventLevel.Debug, "******************* Initializing System **********************"); - - startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite); - - initializeEvent = new ManualResetEventSlim(false); - - DeviceManager.AllDevicesRegistered += (o, a) => - { - initializeEvent.Set(); - }; - - initializeEvent.Wait(30000); - - Debug.LogMessage(LogEventLevel.Debug, "******************* System Initialization Complete **********************"); - - SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; - } - else - { - startTimer = new Timer(StartSystem, preventInitializationComplete, StartupTime, Timeout.Infinite); - } - } - - private void StartSystem(object preventInitialization) - { - try + private void StartSystem(object preventInitialization) { DeterminePlatform(); - // Print .NET runtime version - Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); - if (Debug.DoNotLoadConfigOnNextBoot) { - CrestronConsole.AddNewConsoleCommand(s => Task.Run(() => GoWithLoad()), "go", "Loads configuration file", + CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file", ConsoleAccessLevelEnum.AccessOperator); } @@ -153,7 +112,7 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization CrestronConsole.AddNewConsoleCommand(s => Debug.LogMessage(LogEventLevel.Information, "CONSOLE MESSAGE: {0}", s), "appdebugmessage", "Writes message to log", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(ListTieLines, - "listtielines", "Prints out all tie lines. Usage: listtielines [signaltype]", ConsoleAccessLevelEnum.AccessOperator); + "listtielines", "Prints out all tie lines. Usage: listtielines [signaltype]", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(VisualizeRoutes, "visualizeroutes", "Visualizes routes by signal type", @@ -163,19 +122,12 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization "Visualizes current active routes from DefaultCollection", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => - { - foreach (var tl in TieLineCollection.Default) - CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); - }, - "listtielines", "Prints out all tie lines", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(s => { CrestronConsole.ConsoleCommandResponse ("Current running configuration. This is the merged system and template configuration" + CrestronEnvironment.NewLine); CrestronConsole.ConsoleCommandResponse(Newtonsoft.Json.JsonConvert.SerializeObject - (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented)); + (ConfigReader.ConfigObject, Newtonsoft.Json.Formatting.Indented).Replace(Environment.NewLine, "\r\n")); }, "showconfig", "Shows the current running merged config", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand( @@ -188,9 +140,6 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts, "getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator); - CrestronConsole.AddNewConsoleCommand(PrintInitializationExceptions, - "getinitexceptions", "Reports any exceptions that occurred during initialization", ConsoleAccessLevelEnum.AccessOperator); - DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API")); if (!Debug.DoNotLoadConfigOnNextBoot) @@ -204,149 +153,151 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } } - catch (Exception e) + + private void PrintPortalInfo(string args) { - InitializationExceptions.Add(e); - Debug.LogFatal(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); - } - } - - - private void PrintPortalInfo(string args) - { - if (ConfigReader.ConfigObject == null) - { - CrestronConsole.ConsoleCommandResponse("No configuration loaded. Cannot show portal URLs."); - return; - } - - if (string.IsNullOrEmpty(ConfigReader.ConfigObject.SystemUrl) && string.IsNullOrEmpty(ConfigReader.ConfigObject.TemplateUrl)) - { - CrestronConsole.ConsoleCommandResponse("No portal URLs defined in config."); - return; - } - - CrestronConsole.ConsoleCommandResponse( - "This system can be found at the following URLs:{2}" + - "System URL: {0}{2}" + - "Template URL: {1}{2}", - ConfigReader.ConfigObject?.SystemUrl, - ConfigReader.ConfigObject?.TemplateUrl, - CrestronEnvironment.NewLine); - } - - /// - /// Determines if the program is running on a processor (appliance) or server (VC-4). - /// - /// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform - /// - public void DeterminePlatform() - { - try - { - Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); - - string filePathPrefix; - - var dirSeparator = Global.DirectorySeparator; - - string directoryPrefix; - - directoryPrefix = Directory.GetApplicationRootDirectory(); - - Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly())); - - if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS + if(ConfigReader.ConfigObject == null) { - string userFolder = "user"; - string nvramFolder = "nvram"; + CrestronConsole.ConsoleCommandResponse("No configuration loaded. Cannot show portal URLs."); + return; + } - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series"); - //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); + if (string.IsNullOrEmpty(ConfigReader.ConfigObject.SystemUrl) && string.IsNullOrEmpty(ConfigReader.ConfigObject.TemplateUrl)) + { + CrestronConsole.ConsoleCommandResponse("No portal URLs defined in config."); + return; + } - // Check if User/ProgramX exists - if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + CrestronConsole.ConsoleCommandResponse( + "This system can be found at the following URLs:{2}" + + "System URL: {0}{2}" + + "Template URL: {1}{2}", + ConfigReader.ConfigObject?.SystemUrl, + ConfigReader.ConfigObject?.TemplateUrl, + CrestronEnvironment.NewLine); + } + + /// + /// DeterminePlatform method + /// + public void DeterminePlatform() + { + try + { + Debug.LogMessage(LogEventLevel.Information, "Determining Platform..."); + + string filePathPrefix; + + var dirSeparator = Global.DirectorySeparator; + + string directoryPrefix; + + directoryPrefix = Directory.GetApplicationRootDirectory(); + + Global.SetAssemblyVersion(PluginLoader.GetAssemblyVersion(Assembly.GetExecutingAssembly())); + + if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS { + string userFolder; + string nvramFolder; + bool is4series = false; - Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); - filePathPrefix = directoryPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + if (eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4)) // Handle 4-series + { + is4series = true; + // Set path to user/ + userFolder = "user"; + nvramFolder = "nvram"; + } + else + { + userFolder = "User"; + nvramFolder = "Nvram"; + } + + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); + //Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series"); + + // Check if User/ProgramX exists + if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + + Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); + filePathPrefix = directoryPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // Check if Nvram/Programx exists + else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + { + Debug.LogMessage(LogEventLevel.Information, "{nvramFolder:l}/program{applicationNumber} directory found", nvramFolder, InitialParametersClass.ApplicationNumber); + + filePathPrefix = directoryPrefix + dirSeparator + nvramFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } + // If neither exists, set path to User/ProgramX + else + { + Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); + + filePathPrefix = directoryPrefix + dirSeparator + userFolder + + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + } } - // Check if Nvram/Programx exists - else if (Directory.Exists(directoryPrefix + dirSeparator + nvramFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) + else // Handles Linux OS (Virtual Control) { - Debug.LogMessage(LogEventLevel.Information, "{nvramFolder:l}/program{applicationNumber} directory found", nvramFolder, InitialParametersClass.ApplicationNumber); + //Debug.SetDebugLevel(2); + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); - filePathPrefix = directoryPrefix + dirSeparator + nvramFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; + // Set path to User/ + filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; + } + + Global.SetFilePathPrefix(filePathPrefix); + } + catch (Exception e) + { + Debug.LogMessage(e, "Unable to determine platform due to exception"); + } + } + + /// + /// GoWithLoad method + /// + public void GoWithLoad() + { + try + { + Debug.SetDoNotLoadConfigOnNextBoot(false); + + PluginLoader.AddProgramAssemblies(); + + _ = new Core.DeviceFactory(); + + LoadAssets(Global.ApplicationDirectoryPathPrefix, Global.FilePathPrefix); + + Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); + + var filesReady = SetupFilesystem(); + if (filesReady) + { + Debug.LogMessage(LogEventLevel.Information, "Checking for plugins"); + PluginLoader.LoadPlugins(); + + Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config..."); + if (!ConfigReader.LoadConfig2() || ConfigReader.ConfigObject == null) + { + Debug.LogMessage(LogEventLevel.Warning, "Unable to load config file."); + } + + Load(); + Debug.LogMessage(LogEventLevel.Information, "Essentials load complete"); } - // If neither exists, set path to User/ProgramX else { - Debug.LogMessage(LogEventLevel.Information, "{userFolder:l}/program{applicationNumber} directory found", userFolder, InitialParametersClass.ApplicationNumber); - - filePathPrefix = directoryPrefix + dirSeparator + userFolder - + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber) + dirSeparator; - } - } - else // Handles Linux OS (Virtual Control) - { - //Debug.SetDebugLevel(2); - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on Virtual Control Server", Global.AssemblyVersion); - - // Set path to User/ - filePathPrefix = directoryPrefix + dirSeparator + "User" + dirSeparator; - } - - Global.SetFilePathPrefix(filePathPrefix); - } - catch (Exception e) - { - InitializationExceptions.Add(e); - Debug.LogMessage(e, "Unable to determine platform due to exception"); - } - } - - /// - /// Begins the process of loading resources including plugins and configuration data - /// - public void GoWithLoad() - { - try - { - Debug.SetDoNotLoadConfigOnNextBoot(false); - - PluginLoader.AddProgramAssemblies(); - - _ = new Core.DeviceFactory(); - - LoadAssets(Global.ApplicationDirectoryPathPrefix, Global.FilePathPrefix); - - Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration"); - - var filesReady = SetupFilesystem(); - if (filesReady) - { - Debug.LogMessage(LogEventLevel.Information, "Checking for plugins"); - PluginLoader.LoadPlugins(); - - Debug.LogMessage(LogEventLevel.Information, "Folder structure verified. Loading config..."); - if (!ConfigReader.LoadConfig() || ConfigReader.ConfigObject == null) - { - Debug.LogMessage(LogEventLevel.Warning, "Unable to load config file."); - } - - CheckPluginVersionsAgainstConfig(); - - Load(); - Debug.LogMessage(LogEventLevel.Information, "Essentials load complete"); - } - else - { - Debug.LogMessage(LogEventLevel.Information, - @"---------------------------------------------- + Debug.LogMessage(LogEventLevel.Information, + @"---------------------------------------------- ------------------------------------------------ ------------------------------------------------ Essentials file structure setup completed. @@ -355,241 +306,173 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization ------------------------------------------------ ------------------------------------------------ ------------------------------------------------"); - } - - } - catch (Exception e) - { - InitializationExceptions.Add(e); - Debug.LogFatal(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); - } - finally - { - // Notify the OS that the program intitialization has completed - SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; - } - - } - - - private void CheckPluginVersionsAgainstConfig() - { - try - { - Debug.LogInformation("Checking plugin versions against config..."); - - if (ConfigReader.ConfigObject == null) - return; - - var versions = ConfigReader.ConfigObject.Versions; - - if (versions == null) - return; - - var pluginVersions = PluginLoader.EssentialsPluginAssemblies - .Select(a => - { - var packageId = a.Assembly.GetCustomAttributes() - .FirstOrDefault(attr => attr.Key == "NuGetPackageId")?.Value - ?? a.Name; - return (packageId, version: a.Version.ToString()); - }) - .ToDictionary(x => x.packageId, x => x.version); - - if (versions.Essentials != null) - { - if (pluginVersions.TryGetValue("PepperDashEssentials", out var pluginVersion)) - { - if (pluginVersion != versions.Essentials.PackageId) - { - Debug.LogMessage(LogEventLevel.Warning, - "Essentials version mismatch. Config version: {configVersion:l}, Loaded plugin version: {pluginVersion:l}", - versions.Essentials, pluginVersion); - } - } - } - - if (versions.Packages == null) - return; - - foreach (var version in versions.Packages) - { - if (pluginVersions.TryGetValue(version.PackageId, out var pluginVersion)) - { - if (pluginVersion != version.Version) - { - Debug.LogMessage(LogEventLevel.Warning, - "Plugin version mismatch for {pluginKey:l}. Config version: {configVersion:l}, Loaded plugin version: {pluginVersion:l}", - version.PackageId, version.Version, pluginVersion); - } - } - else - { - Debug.LogMessage(LogEventLevel.Warning, - "Plugin {pluginKey:l} specified in config versions but not found in loaded plugins.", version.PackageId); - } - } - } - catch (Exception ex) - { - InitializationExceptions.Add(ex); - Debug.LogMessage(ex, "Error checking plugin versions against config. Continuing with load."); - } - } - - /// - /// Verifies filesystem is set up. IR, SGD, and programX folders - /// - bool SetupFilesystem() - { - Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure"); - var configDir = Global.FilePathPrefix; - - Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir); - var configExists = Directory.Exists(configDir); - if (!configExists) - Directory.Create(configDir); - - var irDir = Global.FilePathPrefix + "ir"; - if (!Directory.Exists(irDir)) - Directory.Create(irDir); - - var sgdDir = Global.FilePathPrefix + "sgd"; - if (!Directory.Exists(sgdDir)) - Directory.Create(sgdDir); - - var pluginDir = Global.FilePathPrefix + "plugins"; - if (!Directory.Exists(pluginDir)) - Directory.Create(pluginDir); - - var joinmapDir = Global.FilePathPrefix + "joinmaps"; - if (!Directory.Exists(joinmapDir)) - Directory.Create(joinmapDir); - - return configExists; - } - - /// - /// TearDown method - /// - public void TearDown() - { - Debug.LogMessage(LogEventLevel.Information, "Tearing down existing system"); - DeviceManager.DeactivateAll(); - - TieLineCollection.Default.Clear(); - - foreach (var key in DeviceManager.GetDevices()) - DeviceManager.RemoveDevice(key); - - Debug.LogMessage(LogEventLevel.Information, "Tear down COMPLETE"); - } - - - /// - /// Load method - /// - void Load() - { - LoadDevices(); - LoadRooms(); - - DeviceManager.ActivateAll(); - - LoadTieLines(); - - } - - /// - /// Reads all devices from config and adds them to DeviceManager - /// - public void LoadDevices() - { - - // Build the processor wrapper class - DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); - - DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); - - // Add global System Monitor device - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) - { - DeviceManager.AddDevice( - new Core.Monitoring.SystemMonitorController("systemMonitor")); - } - - if (ConfigReader.ConfigObject is null) - { - Debug.LogMessage(LogEventLevel.Warning, "LoadDevices: ConfigObject is null. Cannot load devices."); - return; - } - - Debug.LogInformation("Loading devices from config."); - - foreach (var devConf in ConfigReader.ConfigObject.Devices) - { - IKeyed newDev = null; - - try - { - Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type); - // Skip this to prevent unnecessary warnings - if (devConf.Key == "processor") - { - var prompt = Global.ControlSystem.ControllerPrompt; - - var typeMatch = string.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) || - string.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase); - - if (!typeMatch) - Debug.LogMessage(LogEventLevel.Information, - "WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available", - devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper()); - - - continue; } - - if (newDev == null) - newDev = Core.DeviceFactory.GetDevice(devConf); - - if (newDev != null) - DeviceManager.AddDevice(newDev); - else - Debug.LogMessage(LogEventLevel.Information, "ERROR: Cannot load unknown device type '{deviceType:l}', key '{deviceKey:l}'.", devConf.Type, devConf.Key); } catch (Exception e) { - InitializationExceptions.Add(e); - Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.", args: new[] { devConf.Key }); + Debug.LogMessage(e, "FATAL INITIALIZE ERROR. System is in an inconsistent state"); + } + finally + { + // Notify the OS that the program intitialization has completed + SystemMonitor.ProgramInitialization.ProgramInitializationComplete = true; } - } - Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); - return; - - - } - - - /// - /// Helper method to load tie lines. This should run after devices have loaded - /// - public void LoadTieLines() - { - // In the future, we can't necessarily just clear here because devices - // might be making their own internal sources/tie lines - - var tlc = TieLineCollection.Default; - - if (ConfigReader.ConfigObject?.TieLines == null) - { - return; } - try + + + /// + /// Verifies filesystem is set up. IR, SGD, and programX folders + /// + bool SetupFilesystem() { + Debug.LogMessage(LogEventLevel.Information, "Verifying and/or creating folder structure"); + var configDir = Global.FilePathPrefix; + + Debug.LogMessage(LogEventLevel.Information, "FilePathPrefix: {filePathPrefix:l}", configDir); + var configExists = Directory.Exists(configDir); + if (!configExists) + Directory.Create(configDir); + + var irDir = Global.FilePathPrefix + "ir"; + if (!Directory.Exists(irDir)) + Directory.Create(irDir); + + var sgdDir = Global.FilePathPrefix + "sgd"; + if (!Directory.Exists(sgdDir)) + Directory.Create(sgdDir); + + var pluginDir = Global.FilePathPrefix + "plugins"; + if (!Directory.Exists(pluginDir)) + Directory.Create(pluginDir); + + var joinmapDir = Global.FilePathPrefix + "joinmaps"; + if (!Directory.Exists(joinmapDir)) + Directory.Create(joinmapDir); + + return configExists; + } + + /// + /// TearDown method + /// + public void TearDown() + { + Debug.LogMessage(LogEventLevel.Information, "Tearing down existing system"); + DeviceManager.DeactivateAll(); + + TieLineCollection.Default.Clear(); + + foreach (var key in DeviceManager.GetDevices()) + DeviceManager.RemoveDevice(key); + + Debug.LogMessage(LogEventLevel.Information, "Tear down COMPLETE"); + } + + /// + /// + /// + void Load() + { + LoadDevices(); + LoadRooms(); + LoadLogoServer(); + + DeviceManager.ActivateAll(); + + LoadTieLines(); + + /*var mobileControl = GetMobileControlDevice(); + + if (mobileControl == null) return; + + mobileControl.LinkSystemMonitorToAppServer();*/ + + } + + /// + /// LoadDevices method + /// + public void LoadDevices() + { + + // Build the processor wrapper class + DeviceManager.AddDevice(new Core.Devices.CrestronProcessor("processor")); + + DeviceManager.AddDevice(new RoutingFeedbackManager($"routingFeedbackManager", "Routing Feedback Manager")); + + // Add global System Monitor device + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + DeviceManager.AddDevice( + new Core.Monitoring.SystemMonitorController("systemMonitor")); + } + + if (ConfigReader.ConfigObject is null) + { + Debug.LogMessage(LogEventLevel.Warning, "LoadDevices: ConfigObject is null. Cannot load devices."); + return; + } + + foreach (var devConf in ConfigReader.ConfigObject.Devices) + { + IKeyed newDev = null; + + try + { + Debug.LogMessage(LogEventLevel.Information, "Creating device '{deviceKey:l}', type '{deviceType:l}'", devConf.Key, devConf.Type); + // Skip this to prevent unnecessary warnings + if (devConf.Key == "processor") + { + var prompt = Global.ControlSystem.ControllerPrompt; + + var typeMatch = string.Equals(devConf.Type, prompt, StringComparison.OrdinalIgnoreCase) || + string.Equals(devConf.Type, prompt.Replace("-", ""), StringComparison.OrdinalIgnoreCase); + + if (!typeMatch) + Debug.LogMessage(LogEventLevel.Information, + "WARNING: Config file defines processor type as '{deviceType:l}' but actual processor is '{processorType:l}'! Some ports may not be available", + devConf.Type.ToUpper(), Global.ControlSystem.ControllerPrompt.ToUpper()); + + + continue; + } + + + if (newDev == null) + newDev = Core.DeviceFactory.GetDevice(devConf); + + if (newDev != null) + DeviceManager.AddDevice(newDev); + else + Debug.LogMessage(LogEventLevel.Information, "ERROR: Cannot load unknown device type '{deviceType:l}', key '{deviceKey:l}'.", devConf.Type, devConf.Key); + } + catch (Exception e) + { + Debug.LogMessage(e, "ERROR: Creating device {deviceKey:l}. Skipping device.", args: new[] { devConf.Key }); + } + } + Debug.LogMessage(LogEventLevel.Information, "All Devices Loaded."); + + } + + + /// + /// LoadTieLines method + /// + public void LoadTieLines() + { + // In the future, we can't necessarily just clear here because devices + // might be making their own internal sources/tie lines + + var tlc = TieLineCollection.Default; + + if (ConfigReader.ConfigObject?.TieLines == null) + { + return; + } foreach (var tieLineConfig in ConfigReader.ConfigObject.TieLines) { @@ -597,354 +480,389 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig, IInitialization if (newTL != null) tlc.Add(newTL); } - } - catch (Exception e) - { - InitializationExceptions.Add(e); - Debug.LogMessage(e, "ERROR: Creating tie line. Skipping tie line."); + + Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded."); + + Extensions.MapDestinationsToSources(); + + Debug.LogMessage(LogEventLevel.Information, "All Routes Mapped."); } - Debug.LogMessage(LogEventLevel.Information, "All Tie Lines Loaded."); - Extensions.MapDestinationsToSources(); - - Debug.LogMessage(LogEventLevel.Information, "All Routes Mapped."); - } - - - - /// - /// Visualizes routes in a tree format for better understanding of signal paths - /// - private void ListTieLines(string args) - { - try - { - if (args.Contains("?")) - { - CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n"); - CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); - return; - } - - eRoutingSignalType? signalTypeFilter = null; - if (!string.IsNullOrEmpty(args)) - { - eRoutingSignalType parsedType; - if (Enum.TryParse(args.Trim(), true, out parsedType)) - { - signalTypeFilter = parsedType; - } - else - { - CrestronConsole.ConsoleCommandResponse("Invalid signal type: {0}\r\n", args.Trim()); - CrestronConsole.ConsoleCommandResponse("Valid types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); - return; - } - } - - var tielines = signalTypeFilter.HasValue - ? TieLineCollection.Default.Where(tl => tl.Type.HasFlag(signalTypeFilter.Value)) - : TieLineCollection.Default; - - var count = 0; - foreach (var tl in tielines) - { - CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); - count++; - } - - CrestronConsole.ConsoleCommandResponse("\r\nTotal: {0} tieline{1}{2}", count, count == 1 ? "" : "s", CrestronEnvironment.NewLine); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Error listing tielines: {0}\r\n", ex.Message); - } - } - - private void VisualizeRoutes(string args) - { - try - { - if (args.Contains("?")) - { - CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n"); - CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); - CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); - CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); - return; - } - - ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); - - CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); - CrestronConsole.ConsoleCommandResponse("| ROUTE VISUALIZATION |\r\n"); - CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); - - foreach (var descriptorCollection in Extensions.RouteDescriptors.Where(kv => kv.Value.Descriptors.Count() > 0)) - { - // Filter by signal type if specified - if (signalTypeFilter.HasValue && descriptorCollection.Key != signalTypeFilter.Value) - continue; - - CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", - descriptorCollection.Key, - descriptorCollection.Value.Descriptors.Count()); - - foreach (var descriptor in descriptorCollection.Value.Descriptors) - { - // Filter by source/dest if specified - if (sourceFilter != null && !descriptor.Source.Key.ToLower().Contains(sourceFilter)) - continue; - if (destFilter != null && !descriptor.Destination.Key.ToLower().Contains(destFilter)) - continue; - - VisualizeRouteDescriptor(descriptor); - } - } - - CrestronConsole.ConsoleCommandResponse("\r\n"); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Error visualizing routes: {0}\r\n", ex.Message); - } - } - - private void VisualizeCurrentRoutes(string args) - { - try - { - if (args.Contains("?")) - { - CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n"); - CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); - CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); - CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); - return; - } - - ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); - - CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); - CrestronConsole.ConsoleCommandResponse("| CURRENT ROUTES VISUALIZATION |\r\n"); - CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); - - var hasRoutes = false; - - // Get all descriptors from DefaultCollection - var allDescriptors = RouteDescriptorCollection.DefaultCollection.Descriptors; - - // Group by signal type - var groupedByType = allDescriptors.GroupBy(d => d.SignalType); - - foreach (var group in groupedByType) - { - var signalType = group.Key; - - // Filter by signal type if specified - if (signalTypeFilter.HasValue && signalType != signalTypeFilter.Value) - continue; - - var filteredDescriptors = group.Where(d => - { - if (sourceFilter != null && !d.Source.Key.ToLower().Contains(sourceFilter)) - return false; - if (destFilter != null && !d.Destination.Key.ToLower().Contains(destFilter)) - return false; - return true; - }).ToList(); - - if (filteredDescriptors.Count == 0) - continue; - - hasRoutes = true; - CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", - signalType, - filteredDescriptors.Count); - - foreach (var descriptor in filteredDescriptors) - { - VisualizeRouteDescriptor(descriptor); - } - } - - if (!hasRoutes) - { - CrestronConsole.ConsoleCommandResponse("\r\nNo active routes found in current state.\r\n"); - } - - CrestronConsole.ConsoleCommandResponse("\r\n"); - } - catch (Exception ex) - { - CrestronConsole.ConsoleCommandResponse("Error visualizing current state: {0}\r\n", ex.Message); - } - } - - /// - /// Parses route filter arguments from command line - /// - /// Command line arguments - /// Parsed signal type filter (if any) - /// Parsed source filter (if any) - /// Parsed destination filter (if any) - private void ParseRouteFilters(string args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter) - { - signalTypeFilter = null; - sourceFilter = null; - destFilter = null; - - if (string.IsNullOrEmpty(args)) - return; - - var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - for (int i = 0; i < parts.Length; i++) - { - var part = parts[i]; - - // Check for flags - if (part == "-s" && i + 1 < parts.Length) - { - sourceFilter = parts[++i].ToLower(); - } - else if (part == "-d" && i + 1 < parts.Length) - { - destFilter = parts[++i].ToLower(); - } - // Try to parse as signal type if not a flag and no signal type set yet - else if (!part.StartsWith("-") && !signalTypeFilter.HasValue) - { - if (Enum.TryParse(part, true, out eRoutingSignalType parsedType)) - { - signalTypeFilter = parsedType; - } - } - } - } - - /// - /// Visualizes a single route descriptor in a tree format - /// - private void VisualizeRouteDescriptor(RouteDescriptor descriptor) - { - CrestronConsole.ConsoleCommandResponse("|\r\n"); - CrestronConsole.ConsoleCommandResponse("|-- {0} --> {1}\r\n", - descriptor.Source.Key, - descriptor.Destination.Key); - - if (descriptor.Routes == null || descriptor.Routes.Count == 0) - { - CrestronConsole.ConsoleCommandResponse("| +-- (No switching steps)\r\n"); - return; - } - - for (int i = 0; i < descriptor.Routes.Count; i++) - { - var route = descriptor.Routes[i]; - var isLast = i == descriptor.Routes.Count - 1; - var prefix = isLast ? "+" : "|"; - var continuation = isLast ? " " : "|"; - - if (route.SwitchingDevice != null) - { - CrestronConsole.ConsoleCommandResponse("| {0}-- [{1}] {2}\r\n", - prefix, - route.SwitchingDevice.Key, - GetSwitchDescription(route)); - - // Add visual connection line for non-last items - if (!isLast) - CrestronConsole.ConsoleCommandResponse("| {0} |\r\n", continuation); - } - else - { - CrestronConsole.ConsoleCommandResponse("| {0}-- {1}\r\n", prefix, route.ToString()); - } - } - } - - /// - /// Gets a readable description of the switching operation - /// - private string GetSwitchDescription(RouteSwitchDescriptor route) - { - if (route.OutputPort != null && route.InputPort != null) - { - return string.Format("{0} -> {1}", route.OutputPort.Key, route.InputPort.Key); - } - else if (route.InputPort != null) - { - return string.Format("-> {0}", route.InputPort.Key); - } - else - { - return "(passthrough)"; - } - } - - /// - /// Reads all rooms from config and adds them to DeviceManager - /// - public void LoadRooms() - { - if (ConfigReader.ConfigObject?.Rooms == null) - { - Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration."); - return; - } - - foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) + /// + /// Visualizes routes in a tree format for better understanding of signal paths + /// + private void ListTieLines(string args) { try { - var room = Core.DeviceFactory.GetDevice(roomConfig); - - if (room == null) + if (!string.IsNullOrEmpty(args) && args.Contains("?")) { - Debug.LogWarning("ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key); - continue; + CrestronConsole.ConsoleCommandResponse("Usage: listtielines [signaltype]\r\n"); + CrestronConsole.ConsoleCommandResponse("Signal types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); + return; } - DeviceManager.AddDevice(room); + eRoutingSignalType? signalTypeFilter = null; + if (!string.IsNullOrEmpty(args)) + { + eRoutingSignalType parsedType; + if (Enum.TryParse(args.Trim(), true, out parsedType)) + { + signalTypeFilter = parsedType; + } + else + { + CrestronConsole.ConsoleCommandResponse("Invalid signal type: {0}\r\n", args.Trim()); + CrestronConsole.ConsoleCommandResponse("Valid types: Audio, Video, SecondaryAudio, AudioVideo, UsbInput, UsbOutput\r\n"); + return; + } + } + + var tielines = signalTypeFilter.HasValue + ? TieLineCollection.Default.Where(tl => tl.Type.HasFlag(signalTypeFilter.Value)) + : TieLineCollection.Default; + + var count = 0; + foreach (var tl in tielines) + { + CrestronConsole.ConsoleCommandResponse(" {0}{1}", tl, CrestronEnvironment.NewLine); + count++; + } + + CrestronConsole.ConsoleCommandResponse("\r\nTotal: {0} tieline{1}{2}", count, count == 1 ? "" : "s", CrestronEnvironment.NewLine); } catch (Exception ex) { - InitializationExceptions.Add(ex); - Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); - continue; + CrestronConsole.ConsoleCommandResponse("Error listing tielines: {0}\r\n", ex.Message); } } - Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded."); + private void VisualizeRoutes(string args) + { + try + { + if (!string.IsNullOrEmpty(args) && args.Contains("?")) + { + CrestronConsole.ConsoleCommandResponse("Usage: visualizeroutes [signaltype] [-s source] [-d destination]\r\n"); + CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); + CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); + CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); + return; + } + + ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); + + CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); + CrestronConsole.ConsoleCommandResponse("| ROUTE VISUALIZATION |\r\n"); + CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); + + foreach (var descriptorCollection in Extensions.RouteDescriptors.Where(kv => kv.Value.Descriptors.Count() > 0)) + { + // Filter by signal type if specified + if (signalTypeFilter.HasValue && descriptorCollection.Key != signalTypeFilter.Value) + continue; + + CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", + descriptorCollection.Key, + descriptorCollection.Value.Descriptors.Count()); + + foreach (var descriptor in descriptorCollection.Value.Descriptors) + { + // Filter by source/dest if specified + if (sourceFilter != null && !descriptor.Source.Key.ToLower().Contains(sourceFilter)) + continue; + if (destFilter != null && !descriptor.Destination.Key.ToLower().Contains(destFilter)) + continue; + + VisualizeRouteDescriptor(descriptor); + } + } + + CrestronConsole.ConsoleCommandResponse("\r\n"); + } + catch (Exception ex) + { + CrestronConsole.ConsoleCommandResponse("Error visualizing routes: {0}\r\n", ex.Message); + } + } + + private void VisualizeCurrentRoutes(string args) + { + try + { + if (!string.IsNullOrEmpty(args) && args.Contains("?")) + { + CrestronConsole.ConsoleCommandResponse("Usage: visualizecurrentroutes [signaltype] [-s source] [-d destination]\r\n"); + CrestronConsole.ConsoleCommandResponse(" signaltype: Audio, Video, AudioVideo, etc.\r\n"); + CrestronConsole.ConsoleCommandResponse(" -s: Filter by source key (partial match)\r\n"); + CrestronConsole.ConsoleCommandResponse(" -d: Filter by destination key (partial match)\r\n"); + return; + } + + ParseRouteFilters(args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter); + + CrestronConsole.ConsoleCommandResponse("\r\n+===========================================================================+\r\n"); + CrestronConsole.ConsoleCommandResponse("| CURRENT ROUTES VISUALIZATION |\r\n"); + CrestronConsole.ConsoleCommandResponse("+===========================================================================+\r\n\r\n"); + + var hasRoutes = false; + + // Get all descriptors from DefaultCollection + var allDescriptors = RouteDescriptorCollection.DefaultCollection.Descriptors; + + // Group by signal type + var groupedByType = allDescriptors.GroupBy(d => d.SignalType); + + foreach (var group in groupedByType) + { + var signalType = group.Key; + + // Filter by signal type if specified + if (signalTypeFilter.HasValue && signalType != signalTypeFilter.Value) + continue; + + var filteredDescriptors = group.Where(d => + { + if (sourceFilter != null && !d.Source.Key.ToLower().Contains(sourceFilter)) + return false; + if (destFilter != null && !d.Destination.Key.ToLower().Contains(destFilter)) + return false; + return true; + }).ToList(); + + if (filteredDescriptors.Count == 0) + continue; + + hasRoutes = true; + CrestronConsole.ConsoleCommandResponse("\r\n+--- Signal Type: {0} ({1} routes) ---\r\n", + signalType, + filteredDescriptors.Count); + + foreach (var descriptor in filteredDescriptors) + { + VisualizeRouteDescriptor(descriptor); + } + } + + if (!hasRoutes) + { + CrestronConsole.ConsoleCommandResponse("\r\nNo active routes found in current state.\r\n"); + } + + CrestronConsole.ConsoleCommandResponse("\r\n"); + } + catch (Exception ex) + { + CrestronConsole.ConsoleCommandResponse("Error visualizing current state: {0}\r\n", ex.Message); + } + } + + /// + /// Parses route filter arguments from command line + /// + /// Command line arguments + /// Parsed signal type filter (if any) + /// Parsed source filter (if any) + /// Parsed destination filter (if any) + private void ParseRouteFilters(string args, out eRoutingSignalType? signalTypeFilter, out string sourceFilter, out string destFilter) + { + signalTypeFilter = null; + sourceFilter = null; + destFilter = null; + + if (string.IsNullOrEmpty(args)) + return; + + var parts = args.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < parts.Length; i++) + { + var part = parts[i]; + + // Check for flags + if (part == "-s" && i + 1 < parts.Length) + { + sourceFilter = parts[++i].ToLower(); + } + else if (part == "-d" && i + 1 < parts.Length) + { + destFilter = parts[++i].ToLower(); + } + // Try to parse as signal type if not a flag and no signal type set yet + else if (!part.StartsWith("-") && !signalTypeFilter.HasValue) + { + if (Enum.TryParse(part, true, out eRoutingSignalType parsedType)) + { + signalTypeFilter = parsedType; + } + } + } + } + + /// + /// Visualizes a single route descriptor in a tree format + /// + private void VisualizeRouteDescriptor(RouteDescriptor descriptor) + { + CrestronConsole.ConsoleCommandResponse("|\r\n"); + CrestronConsole.ConsoleCommandResponse("|-- {0} --> {1}\r\n", + descriptor.Source.Key, + descriptor.Destination.Key); + + if (descriptor.Routes == null || descriptor.Routes.Count == 0) + { + CrestronConsole.ConsoleCommandResponse("| +-- (No switching steps)\r\n"); + return; + } + + for (int i = 0; i < descriptor.Routes.Count; i++) + { + var route = descriptor.Routes[i]; + var isLast = i == descriptor.Routes.Count - 1; + var prefix = isLast ? "+" : "|"; + var continuation = isLast ? " " : "|"; + + if (route.SwitchingDevice != null) + { + CrestronConsole.ConsoleCommandResponse("| {0}-- [{1}] {2}\r\n", + prefix, + route.SwitchingDevice.Key, + GetSwitchDescription(route)); + + // Add visual connection line for non-last items + if (!isLast) + CrestronConsole.ConsoleCommandResponse("| {0} |\r\n", continuation); + } + else + { + CrestronConsole.ConsoleCommandResponse("| {0}-- {1}\r\n", prefix, route.ToString()); + } + } + } + + /// + /// Gets a readable description of the switching operation + /// + private string GetSwitchDescription(RouteSwitchDescriptor route) + { + if (route.OutputPort != null && route.InputPort != null) + { + return string.Format("{0} -> {1}", route.OutputPort.Key, route.InputPort.Key); + } + else if (route.InputPort != null) + { + return string.Format("-> {0}", route.InputPort.Key); + } + else + { + return "(passthrough)"; + } + } + + /// + /// LoadRooms method + /// + public void LoadRooms() + { + if (ConfigReader.ConfigObject?.Rooms == null) + { + Debug.LogMessage(LogEventLevel.Information, "Notice: Configuration contains no rooms - Is this intentional? This may be a valid configuration."); + return; + } + + foreach (var roomConfig in ConfigReader.ConfigObject.Rooms) + { + try + { + var room = Core.DeviceFactory.GetDevice(roomConfig); + + if (room == null) + { + Debug.LogWarning("ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key); + continue; + } + + DeviceManager.AddDevice(room); + } + catch (Exception ex) + { + Debug.LogMessage(ex, "Exception loading room {roomKey}:{roomType}", null, roomConfig.Key, roomConfig.Type); + continue; + } + } + + Debug.LogMessage(LogEventLevel.Information, "All Rooms Loaded."); + + + } + + /// + /// Fires up a logo server if not already running + /// + [Obsolete("Logo server is no longer supported and will be removed in a future release.")] + void LoadLogoServer() + { + if (ConfigReader.ConfigObject?.Rooms == null) + { + Debug.LogMessage(LogEventLevel.Information, "No rooms configured. Bypassing Logo server startup."); + return; + } + + if (ConfigReader.ConfigObject?.Rooms == null || + !ConfigReader.ConfigObject.Rooms.Any( + CheckRoomConfig)) + { + Debug.LogMessage(LogEventLevel.Information, "No rooms configured to use system Logo server. Bypassing Logo server startup"); + return; + } + + try + { + LogoServer = new HttpLogoServer(8080, Global.DirectorySeparator + "html" + Global.DirectorySeparator + "logo"); + } + catch (Exception) + { + Debug.LogMessage(LogEventLevel.Information, "NOTICE: Logo server cannot be started. Likely already running in another program"); + } + } + + private bool CheckRoomConfig(DeviceConfig c) + { + string logoDark = null; + string logoLight = null; + string logo = null; + + try + { + if (c.Properties["logoDark"] != null) + { + logoDark = c.Properties["logoDark"].Value("type"); + } + + if (c.Properties["logoLight"] != null) + { + logoLight = c.Properties["logoLight"].Value("type"); + } + + if (c.Properties["logo"] != null) + { + logo = c.Properties["logo"].Value("type"); + } + + return ((logoDark != null && logoDark == "system") || + (logoLight != null && logoLight == "system") || (logo != null && logo == "system")); + } + catch + { + Debug.LogMessage(LogEventLevel.Information, "Unable to find logo information in any room config"); + return false; + } + } + + internal static void LoadAssets(string applicationDirectoryPath, string filePathPrefix) => + AssetLoader.Load(applicationDirectoryPath, filePathPrefix); + } - - private void PrintInitializationExceptions(string args) - { - if (args.Contains("?")) - { - CrestronConsole.ConsoleCommandResponse("Usage: getinitializationexceptions\r\n"); - CrestronConsole.ConsoleCommandResponse("Reports any exceptions that occurred during initialization.\r\n"); - return; - } - - if (InitializationExceptions.Count == 0) - { - CrestronConsole.ConsoleCommandResponse("No initialization exceptions occurred.\r\n"); - return; - } - - CrestronConsole.ConsoleCommandResponse("Initialization Exceptions:\r\n"); - foreach (var ex in InitializationExceptions) - { - CrestronConsole.ConsoleCommandResponse(" - {0}: {1}\r\n", ex.GetType().FullName, ex.Message); - } - } - - - internal static void LoadAssets(string applicationDirectoryPath, string filePathPrefix) => - AssetLoader.Load(applicationDirectoryPath, filePathPrefix); } - diff --git a/src/PepperDash.Essentials/Factory/DeviceFactory.cs b/src/PepperDash.Essentials/Factory/DeviceFactory.cs index dcfaf26e..e60b3fa4 100644 --- a/src/PepperDash.Essentials/Factory/DeviceFactory.cs +++ b/src/PepperDash.Essentials/Factory/DeviceFactory.cs @@ -7,54 +7,58 @@ using System.Reflection; using PepperDash.Core; using PepperDash.Essentials.Core; -namespace PepperDash.Essentials; - -/// -/// Responsible for loading all of the device types for this library -/// -public class DeviceFactory +namespace PepperDash.Essentials { - - public DeviceFactory() + /// + /// Responsible for loading all of the device types for this library + /// + public class DeviceFactory { - var assy = Assembly.GetExecutingAssembly(); - PluginLoader.AddLoadedAssembly(assy.GetName().Name, assy); - var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); - - if (types != null) + /// + /// Initializes a new instance of the DeviceFactory class and loads all device type factories + /// + public DeviceFactory() { - foreach (var type in types) + var assy = Assembly.GetExecutingAssembly(); + PluginLoader.SetEssentialsAssembly(assy.GetName().Name, assy); + + var types = assy.GetTypes().Where(ct => typeof(IDeviceFactory).IsAssignableFrom(ct) && !ct.IsInterface && !ct.IsAbstract); + + if (types != null) { - try + foreach (var type in types) { - var factory = (IDeviceFactory)Activator.CreateInstance(type); - LoadDeviceFactories(factory); - } - catch (Exception e) - { - Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name); + try + { + var factory = (IDeviceFactory)Activator.CreateInstance(type); + LoadDeviceFactories(factory); + } + catch (Exception e) + { + Debug.LogMessage(Serilog.Events.LogEventLevel.Error, "Unable to load type: '{exception}' DeviceFactory: {factoryName}", e, type.Name); + } } } } - } - /// - /// Loads device factories from the specified plugin device factory and registers them for use. - /// - /// This method retrieves metadata from the provided , including - /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type - /// names are converted to lowercase for registration. - /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. - private static void LoadDeviceFactories(IDeviceFactory deviceFactory) - { - foreach (var typeName in deviceFactory.TypeNames) + /// + /// Loads device factories from the specified plugin device factory and registers them for use. + /// + /// This method retrieves metadata from the provided , including + /// type names, descriptions, and configuration snippets, and registers the factory for each device type. The type + /// names are converted to lowercase for registration. + /// The plugin device factory that provides the device types, descriptions, and factory methods to be registered. + private static void LoadDeviceFactories(IDeviceFactory deviceFactory) { - //Debug.LogMessage(LogEventLevel.Verbose, "Getting Description Attribute from class: '{0}'", typeof(T).FullName); - var descriptionAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) as DescriptionAttribute[]; - string description = descriptionAttribute[0].Description; - var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; - Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + foreach (var typeName in deviceFactory.TypeNames) + { + string description = (deviceFactory.FactoryType.GetCustomAttributes(typeof(DescriptionAttribute), true) is DescriptionAttribute[] descriptionAttribute && descriptionAttribute.Length > 0) + ? descriptionAttribute[0].Description + : "No description available"; + + Core.DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); + } } } } diff --git a/src/PepperDash.Essentials/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs b/src/PepperDash.Essentials/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs deleted file mode 100644 index b6533089..00000000 --- a/src/PepperDash.Essentials/Fusion/EssentialsHuddleSpaceFusionSystemControllerBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace PepperDash.Essentials.Fusion; - -public class EssentialsHuddleSpaceFusionSystemControllerBase -{ - private ClientWebSocket _webSocket; - - public EssentialsHuddleSpaceFusionSystemControllerBase() - { - _webSocket = new ClientWebSocket(); - } - - public async Task ConnectAsync(Uri uri) - { - await _webSocket.ConnectAsync(uri, CancellationToken.None); - } - - public async Task SendAsync(string message) - { - var buffer = System.Text.Encoding.UTF8.GetBytes(message); - await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); - } - - public async Task ReceiveAsync() - { - var buffer = new byte[1024]; - var result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); - } - - public async Task CloseAsync() - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); - } -} diff --git a/src/PepperDash.Essentials/HttpLogoServer.cs b/src/PepperDash.Essentials/HttpLogoServer.cs new file mode 100644 index 00000000..d5d133e6 --- /dev/null +++ b/src/PepperDash.Essentials/HttpLogoServer.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.Net.Http; + +using PepperDash.Core; +using Serilog.Events; + +namespace PepperDash.Essentials +{ + /// + /// HTTP server for serving logo images and files + /// + public class HttpLogoServer + { + /// + /// The HTTP server instance + /// + readonly HttpServer _server; + + /// + /// The directory containing files to serve + /// + readonly string _fileDirectory; + + /// + /// Dictionary mapping file extensions to content types + /// + public static Dictionary ExtensionContentTypes; + + /// + /// Initializes a new instance of the HttpLogoServer class + /// + /// Port number for the HTTP server + /// Directory containing files to serve + public HttpLogoServer(int port, string directory) + { + ExtensionContentTypes = new Dictionary + { + //{ ".css", "text/css" }, + //{ ".htm", "text/html" }, + //{ ".html", "text/html" }, + { ".jpg", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + //{ ".js", "application/javascript" }, + //{ ".json", "application/json" }, + //{ ".map", "application/x-navimap" }, + { ".pdf", "application/pdf" }, + { ".png", "image/png" }, + //{ ".txt", "text/plain" }, + }; + + _server = new HttpServer {Port = port}; + _fileDirectory = directory; + _server.OnHttpRequest += Server_OnHttpRequest; + _server.Open(); + + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + } + + /// + /// Handles incoming HTTP requests and serves files from the configured directory + /// + /// The HTTP server instance + /// HTTP request arguments + void Server_OnHttpRequest(object sender, OnHttpRequestArgs args) + { + var path = args.Request.Path; + Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "HTTP Request with path: '{requestPath:l}'", args.Request.Path); + + try + { + if (File.Exists(_fileDirectory + path)) + { + var filePath = path.Replace('/', '\\'); + var localPath = string.Format(@"{0}{1}", _fileDirectory, filePath); + + Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server attempting to find file: '{localPath:l}'", localPath); + if (File.Exists(localPath)) + { + args.Response.Header.ContentType = GetContentType(new FileInfo(localPath).Extension); + args.Response.ContentStream = new FileStream(localPath, FileMode.Open, FileAccess.Read); + } + else + { + Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server Cannot find file '{localPath:l}'", localPath); + args.Response.ContentString = string.Format("Not found: '{0}'", filePath); + args.Response.Code = 404; + } + } + else + { + Debug.LogMessage(LogEventLevel.Verbose, "HTTP Logo Server: '{file:l}' does not exist", _fileDirectory + path); + args.Response.ContentString = string.Format("Not found: '{0}'", _fileDirectory + path); + args.Response.Code = 404; + } + } + catch (Exception ex) + { + Debug.LogMessage(LogEventLevel.Error, "Exception getting file: {exception}", ex.Message, ex.StackTrace); + Debug.LogMessage(LogEventLevel.Verbose, "Stack Trace: {stackTrace}", ex.StackTrace); + + args.Response.Code = 400; + args.Response.ContentString = string.Format("invalid request"); + } + } + + /// + /// Handles program status events and closes the server when the program is stopping + /// + /// The program status event type + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + _server.Close(); + } + + /// + /// Gets the content type for a file based on its extension + /// + /// The file extension + /// The corresponding content type string + /// + /// GetContentType method + /// + public static string GetContentType(string extension) + { + var type = ExtensionContentTypes.ContainsKey(extension) ? ExtensionContentTypes[extension] : "text/plain"; + return type; + } + } +} \ No newline at end of file diff --git a/src/PepperDash.Essentials/PepperDash.Essentials.csproj b/src/PepperDash.Essentials/PepperDash.Essentials.csproj index 29bf17c7..cb83bf84 100644 --- a/src/PepperDash.Essentials/PepperDash.Essentials.csproj +++ b/src/PepperDash.Essentials/PepperDash.Essentials.csproj @@ -1,22 +1,19 @@  Program - Debug;Release; + Debug;Release;Debug 4.7.2 PepperDash.Essentials PepperDashEssentials - net8 + net472 true - $(ProjectDir)bin\$(Configuration)\ + bin\$(Configuration)\ PepperDash Essentials PepperDashEssentials $(Version) false - - Program - full @@ -28,13 +25,31 @@ bin\$(Configuration)\PepperDashEssentials.xml - - <_Parameter1>PepperDash.Essentials.Tests - + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + - - + +