mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-08-31 19:08:29 +00:00
Compare commits
179 commits
main
...
v3.0.0-fea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d747a3fe6d | ||
|
|
527262d514 | ||
|
|
cfd07eee9c | ||
|
|
6c20d46834 | ||
|
|
e0f7fc9a6e | ||
|
|
592ece5a3d | ||
|
|
4926b005d6 | ||
|
|
695ab00c6e | ||
|
|
33e56d3259 | ||
|
|
fd0a612c1a | ||
|
|
c513115be9 | ||
|
|
7e466fbed1 | ||
|
|
8c908c80e2 | ||
|
|
59d2eeea90 | ||
|
|
72844aa6ca | ||
|
|
cd2dfbd69c | ||
|
|
b92e3cf8ac | ||
|
|
51926c07dd | ||
|
|
e9812f7858 | ||
|
|
0d4fd7773e | ||
|
|
da2a09f7cb | ||
|
|
2588b81f0d | ||
|
|
1a5db87c1e | ||
|
|
7fca4d74bf | ||
|
|
77e6ba7f36 | ||
|
|
61b5ca6297 | ||
|
|
5a183e7333 | ||
|
|
5e0a4c04ad | ||
|
|
a782651e4d | ||
|
|
dac5325ecf | ||
|
|
56208342c9 | ||
|
|
830e207ea7 | ||
|
|
4252e9c8e6 | ||
|
|
42b358862f | ||
|
|
9656e3c408 | ||
|
|
f971a3adff | ||
|
|
6852f47ce2 | ||
|
|
f05a25c3e2 | ||
|
|
079f2beb8b | ||
|
|
7d1e0432cb | ||
|
|
21995db550 | ||
|
|
dddb67317a | ||
|
|
3f8b97bff7 | ||
|
|
4a366a5a51 | ||
|
|
4631d3a537 | ||
|
|
f0b546f97d | ||
|
|
e7406dbead | ||
|
|
2d8e42f566 | ||
|
|
f0025ce56c | ||
|
|
73bc50ef0d | ||
|
|
f68e033850 | ||
|
|
d5432e489d | ||
|
|
94837a60cf | ||
|
|
4ab4ee1680 | ||
|
|
122881100a | ||
|
|
34c82588f0 | ||
|
|
7de2aa7c2e | ||
|
|
f3f7755735 | ||
|
|
d771320fff | ||
|
|
9f18275f81 | ||
|
|
fb4ba8ccfa | ||
|
|
4bb2917b69 | ||
|
|
aae41b5947 | ||
|
|
b945ecb470 | ||
|
|
8748157362 | ||
|
|
77c78af74b | ||
|
|
f7c3ed4b8b | ||
|
|
f4fe8eff90 | ||
|
|
a732c5e08e | ||
|
|
a18f7800d1 | ||
|
|
00c7128778 | ||
|
|
b7b4b56cec | ||
|
|
a1d168c4ed | ||
|
|
b65f895e06 | ||
|
|
be545bbfa4 | ||
|
|
846fd711c5 | ||
|
|
1588cc4667 | ||
|
|
747b595b59 | ||
|
|
cff46532be | ||
|
|
0098673a5e | ||
|
|
c8faa19835 | ||
|
|
fb0afc8c41 | ||
|
|
27b3d9cf58 | ||
|
|
4403e91b79 | ||
|
|
1b2f86eb43 | ||
|
|
2dd99da67f | ||
|
|
22f5558e39 | ||
|
|
c306fd717e | ||
|
|
72c948e760 | ||
|
|
dff50ea544 | ||
|
|
4058040330 | ||
|
|
f2f4662c8d | ||
|
|
b2ddce2cc6 | ||
|
|
041bb5c50e | ||
|
|
a27f604660 | ||
|
|
48e64a291f | ||
|
|
a90b5da61b | ||
|
|
bb029221c0 | ||
|
|
153922c3fe | ||
|
|
a1617fbeb2 | ||
|
|
cd89cf1cc9 | ||
|
|
0244b0e585 | ||
|
|
101d6c6e79 | ||
|
|
ec7db1b510 | ||
|
|
f64b595fd7 | ||
|
|
b9dcec587d | ||
|
|
91aa0efa5f | ||
|
|
b41c30cdd0 | ||
|
|
ab4a243ffb | ||
|
|
e19e69d5c0 | ||
|
|
bad08fa6db | ||
|
|
9a385eef96 | ||
|
|
ff1393b768 | ||
|
|
f80aa95649 | ||
|
|
dfc1d2bb21 | ||
|
|
bc70aa629c | ||
|
|
b0525b0f97 | ||
|
|
2e1bb8b286 | ||
|
|
89a1b662b0 | ||
|
|
dc656bddf1 | ||
|
|
d1501d2dab | ||
|
|
af7411f43b | ||
|
|
7d60c41dbc | ||
|
|
5f4a35d425 | ||
|
|
71cf7ef4df | ||
|
|
ac3b73cf32 | ||
|
|
554916a74a | ||
|
|
3d8ee22f45 | ||
|
|
ec3e2bbc0b | ||
|
|
b85f1dab6b | ||
|
|
4ed5e648c0 | ||
|
|
35d44f91e4 | ||
|
|
fa9dc3a2b1 | ||
|
|
7e3c62b303 | ||
|
|
884b2c2e6e | ||
|
|
daf9b4bda0 | ||
|
|
e818c9ca03 | ||
|
|
5dd6d18fcc | ||
|
|
37961b9eac | ||
|
|
24df4e7a03 | ||
|
|
bfb9838743 | ||
|
|
c98b48ff87 | ||
|
|
5c26438a1c | ||
|
|
7bec96e68b | ||
|
|
7076eafc21 | ||
|
|
b4d53dbe0e | ||
|
|
43a9661e08 | ||
|
|
7137945c94 | ||
|
|
ac393c4885 | ||
|
|
346a5e9e57 | ||
|
|
426ef4ad6b | ||
|
|
bcb65c7fa4 | ||
|
|
c2ff65dce3 | ||
|
|
3feab2593d | ||
|
|
5d90fafbd7 | ||
|
|
7f60dcb4cf | ||
|
|
574e4dfb0f | ||
|
|
76759d35cc | ||
|
|
3ece4f0b7b | ||
|
|
aaa5b0532b | ||
|
|
04c4557528 | ||
|
|
9febbf2557 | ||
|
|
eafade9569 | ||
|
|
4e5b8f3897 | ||
|
|
7bd3ccd54b | ||
|
|
790b5a6fb9 | ||
|
|
fbcd9c84da | ||
|
|
4a2c9fe311 | ||
|
|
bdd398e2e6 | ||
|
|
8be5481ac9 | ||
|
|
dbab427d69 | ||
|
|
db4f540710 | ||
|
|
ce8b08e312 | ||
|
|
13e833b797 | ||
|
|
2be078da18 | ||
|
|
7330ae2e30 | ||
|
|
a57dddba5e | ||
|
|
0bfec16622 | ||
|
|
94e7b8210f |
680 changed files with 55600 additions and 55064 deletions
|
|
@ -6,9 +6,13 @@ on:
|
||||||
- '**'
|
- '**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
runTests:
|
||||||
|
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-run-tests.yml@main
|
||||||
|
secrets: inherit
|
||||||
getVersion:
|
getVersion:
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
needs: runTests
|
||||||
build-4Series:
|
build-4Series:
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
@ -19,4 +23,5 @@ jobs:
|
||||||
version: ${{ needs.getVersion.outputs.version }}
|
version: ${{ needs.getVersion.outputs.version }}
|
||||||
tag: ${{ needs.getVersion.outputs.tag }}
|
tag: ${{ needs.getVersion.outputs.tag }}
|
||||||
channel: ${{ needs.getVersion.outputs.channel }}
|
channel: ${{ needs.getVersion.outputs.channel }}
|
||||||
bypassPackageCheck: true
|
bypassPackageCheck: true
|
||||||
|
devToolsVersion: ${{ vars.ESSENTIALSDEVTOOLSVERSION }}
|
||||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
|
|
@ -26,9 +26,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Dotnet Setup
|
- name: Dotnet Setup
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.x
|
dotnet-version: 8.x
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,49 +36,189 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
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|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -90,6 +230,9 @@ Global
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {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}
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ Utilization of Essentials Framework falls into the following categories:
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For detailed documentation, see the [Wiki](https://github.com/PepperDash/EssentialsFramework/wiki).
|
For detailed documentation, see the [DocFx Site](https://pepperdash.github.io/Essentials/).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ Or use the links to the right to navigate our documentation.
|
||||||
|
|
||||||
## Benefits
|
## Benefits
|
||||||
|
|
||||||
- Runs on Crestron 3-Series, **4-Series** and VC-4 Control System platforms
|
- Runs on Crestron **4-Series** and VC-4 Control System platforms
|
||||||
- Reduced hardware overhead compared to S+ and Simpl solutions
|
- Reduced hardware overhead compared to S+ and Simpl solutions
|
||||||
- Quick development cycle
|
- Quick development cycle
|
||||||
- Shared resources made easily available
|
- Shared resources made easily available
|
||||||
- More flexibility with less code
|
- More flexibility with less code
|
||||||
- Configurable using simple JSON files
|
- Configurable using simple JSON files
|
||||||
|
- Built in websocket communication API for HTML5 frontend applications.
|
||||||
- Is awesome
|
- Is awesome
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -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!
|
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
|
```cs
|
||||||
public override bool CustomActivate()
|
protected override bool CustomActivate()
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks");
|
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks");
|
||||||
SetupFunctions();
|
SetupFunctions();
|
||||||
|
|
@ -52,7 +52,7 @@ We can see in the example below that during the `CustomActivate()` phase, we def
|
||||||
### **Example**
|
### **Example**
|
||||||
|
|
||||||
```cs
|
```cs
|
||||||
public override bool CustomActivate()
|
protected override bool CustomActivate()
|
||||||
{
|
{
|
||||||
foreach (var i in Config.Inputs)
|
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`)
|
### Example (from `PepperDash.Essentials.Devices.Common.VideoCodec.Cisco.CiscoSparkCodec`)
|
||||||
```cs
|
```cs
|
||||||
public override void Initialize()
|
protected override void Initialize()
|
||||||
{
|
{
|
||||||
var socket = Communication as ISocketStatus;
|
var socket = Communication as ISocketStatus;
|
||||||
if (socket != null)
|
if (socket != null)
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,18 @@ When routes are to be executed, Essentials will use this connection graph to dec
|
||||||
|
|
||||||
### Classes Referenced
|
### Classes Referenced
|
||||||
|
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingSource`
|
* `PepperDash.Essentials.Core.IRoutingSource`
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingOutputs`
|
* `PepperDash.Essentials.Core.IRoutingOutputs`
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingInputs`
|
* `PepperDash.Essentials.Core.IRoutingInputs`
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingInputsOutputs`
|
* `PepperDash.Essentials.Core.IRoutingMidpoint`
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingSinkNoSwitching`
|
* `PepperDash.Essentials.Core.IRoutingMidpointWithFeedback`
|
||||||
* `PepperDash.Essentials.Core.Routing.IRoutingSinkWithSwitching`
|
* `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.
|
||||||
|
|
||||||
## Example system, a simple presentation system
|
## Example system, a simple presentation system
|
||||||
|
|
||||||
|
|
@ -56,11 +62,12 @@ 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.
|
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.
|
||||||
|
|
||||||
Midpoint devices must implement `IRoutingInputsOutputs`. Midpoints with switching must implement `IRouting`.
|
Passthrough midpoints (no active switching) must implement `IRoutingMidpoint`. Midpoints that actively
|
||||||
|
switch and report their current routes must implement `IRoutingMidpointWithFeedback`.
|
||||||
|
|
||||||
#### Sink
|
#### 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 `IRoutingSinkNoSwitching` or `IRoutingSinkWithSwitching`.
|
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`.
|
||||||
|
|
||||||
#### Tie-line
|
#### Tie-line
|
||||||
|
|
||||||
|
|
@ -78,4 +85,95 @@ A tie-line is a logical representation of a physical cable connection between tw
|
||||||
|
|
||||||
### Interfaces
|
### Interfaces
|
||||||
|
|
||||||
Todo: Define Interfaces IRouting, IRoutingOutputs, IRoutingInputs
|
#### 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<RoutingOutputPort> OutputPorts`.
|
||||||
|
* `IRoutingInputs` - exposes `RoutingPortCollection<RoutingInputPort> 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<RouteSwitchDescriptor> 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<eRoutingSignalType, IRoutingSource> CurrentSources { get; }`
|
||||||
|
* `Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; }`
|
||||||
|
* `event EventHandler<CurrentSourcesChangedEventArgs> 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<int, IRoutingSinkWithFeedback> 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<MultiviewLayoutStateEventArgs> 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):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "url": "wss://[processor-ip]:[port]/routing/join/", "fallbackUrl": "wss://..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
| 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`) |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
||||||
240
docs/docs/technical-docs/Mobile-Control.md
Normal file
240
docs/docs/technical-docs/Mobile-Control.md
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
# 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 + <program slot number>` 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<T>` 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<MobileControlSimpleContent<uint>>();
|
||||||
|
_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<T>(...)`.
|
||||||
|
* 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<IMobileControl>().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<IHasStartMeetingState>(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.
|
||||||
|
|
@ -38,6 +38,8 @@
|
||||||
href: technical-docs/Plugins.md
|
href: technical-docs/Plugins.md
|
||||||
- name: Communication Basics
|
- name: Communication Basics
|
||||||
href: technical-docs/Communication-Basics.md
|
href: technical-docs/Communication-Basics.md
|
||||||
|
- name: Mobile Control
|
||||||
|
href: technical-docs/Mobile-Control.md
|
||||||
- name: Debugging
|
- name: Debugging
|
||||||
href: technical-docs/Debugging.md
|
href: technical-docs/Debugging.md
|
||||||
- name: Feedback Classes
|
- name: Feedback Classes
|
||||||
|
|
|
||||||
7
runtimeconfig.json
Normal file
7
runtimeconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"configProperties": {
|
||||||
|
"System.Globalization.Invariant": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>2.19.4-local</Version>
|
<Version>3.0.0-local</Version>
|
||||||
<InformationalVersion>$(Version)</InformationalVersion>
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
<Authors>PepperDash Technology</Authors>
|
<Authors>PepperDash Technology</Authors>
|
||||||
<Company>PepperDash Technology</Company>
|
<Company>PepperDash Technology</Company>
|
||||||
<Product>PepperDash Essentials</Product>
|
<Product>PepperDash Essentials</Product>
|
||||||
<Copyright>Copyright © 2025</Copyright>
|
<Copyright>Copyright © 2026</Copyright>
|
||||||
<RepositoryUrl>https://github.com/PepperDash/Essentials</RepositoryUrl>
|
<RepositoryUrl>https://github.com/PepperDash/Essentials</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<PackageTags>Crestron; 4series</PackageTags>
|
<PackageTags>Crestron; 4series</PackageTags>
|
||||||
|
|
@ -20,4 +20,7 @@
|
||||||
<None Include="..\..\LICENSE.md" Pack="true" PackagePath=""/>
|
<None Include="..\..\LICENSE.md" Pack="true" PackagePath=""/>
|
||||||
<None Include="..\..\README.md" Pack="true" PackagePath=""/>
|
<None Include="..\..\README.md" Pack="true" PackagePath=""/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyMetadata Include="PackageId" Value="$(PackageId)" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<Project>
|
<Project>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library'">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != '' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' )">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
|
|
|
||||||
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows pre-registration of Crestron service implementations before the <c>Debug</c>
|
||||||
|
/// static class initialises. Call <see cref="Register"/> from the composition root
|
||||||
|
/// (e.g. ControlSystem constructor) <em>before</em> any code touches <c>Debug.*</c>.
|
||||||
|
/// Test projects should call it with no-op / in-memory implementations so that the
|
||||||
|
/// <c>Debug</c> static constructor never tries to reach the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public static class DebugServiceRegistration
|
||||||
|
{
|
||||||
|
/// <summary>Gets the registered environment abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronEnvironment? Environment { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the registered console abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronConsole? Console { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the registered data-store abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronDataStore? DataStore { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the service implementations that <c>Debug</c> will use when its
|
||||||
|
/// static constructor runs. Any parameter may be <c>null</c> to leave the
|
||||||
|
/// corresponding service unregistered (the <c>Debug</c> class will skip that
|
||||||
|
/// capability gracefully).
|
||||||
|
/// </summary>
|
||||||
|
public static void Register(
|
||||||
|
ICrestronEnvironment? environment,
|
||||||
|
ICrestronConsole? console,
|
||||||
|
ICrestronDataStore? dataStore)
|
||||||
|
{
|
||||||
|
Environment = environment;
|
||||||
|
Console = console;
|
||||||
|
DataStore = dataStore;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eDevicePlatform</c> without requiring the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public enum DevicePlatform
|
||||||
|
{
|
||||||
|
/// <summary>Hardware appliance (e.g. CP4, MC4).</summary>
|
||||||
|
Appliance,
|
||||||
|
/// <summary>Crestron Virtual Control / server runtime.</summary>
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eRuntimeEnvironment</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum RuntimeEnvironment
|
||||||
|
{
|
||||||
|
/// <summary>SimplSharpPro program slot (hardware 4-series).</summary>
|
||||||
|
SimplSharpPro,
|
||||||
|
/// <summary>SimplSharp (older 3-series or server environments).</summary>
|
||||||
|
SimplSharp,
|
||||||
|
/// <summary>Any other environment — check for completeness.</summary>
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>ConsoleAccessLevelEnum</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ConsoleAccessLevel
|
||||||
|
{
|
||||||
|
AccessAdministrator = 0,
|
||||||
|
AccessOperator = 1,
|
||||||
|
AccessProgrammer = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eProgramStatusEventType</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ProgramStatusEventType
|
||||||
|
{
|
||||||
|
Starting,
|
||||||
|
Stopping,
|
||||||
|
Paused,
|
||||||
|
Resumed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the event type used by Crestron's <c>EthernetEventArgs</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum EthernetEventType
|
||||||
|
{
|
||||||
|
LinkDown = 0,
|
||||||
|
LinkUp = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for Crestron ethernet link events.
|
||||||
|
/// </summary>
|
||||||
|
public class PepperDashEthernetEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public EthernetEventType EthernetEventType { get; }
|
||||||
|
public short EthernetAdapter { get; }
|
||||||
|
|
||||||
|
public PepperDashEthernetEventArgs(EthernetEventType eventType, short adapter)
|
||||||
|
{
|
||||||
|
EthernetEventType = eventType;
|
||||||
|
EthernetAdapter = adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the set of <c>CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET</c> values
|
||||||
|
/// used across this codebase — does not aim to be exhaustive.
|
||||||
|
/// </summary>
|
||||||
|
public enum EthernetParameterType
|
||||||
|
{
|
||||||
|
GetCurrentIpAddress,
|
||||||
|
GetHostname,
|
||||||
|
GetDomainName,
|
||||||
|
GetLinkStatus,
|
||||||
|
GetCurrentDhcpState,
|
||||||
|
GetCurrentIpMask,
|
||||||
|
GetCurrentRouter,
|
||||||
|
GetMacAddress,
|
||||||
|
GetDnsServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>SocketStatus</c> without requiring the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public enum PepperDashSocketStatus
|
||||||
|
{
|
||||||
|
SocketNotConnected = 0,
|
||||||
|
SocketConnected = 2,
|
||||||
|
SocketConnectionInProgress = 6,
|
||||||
|
SocketConnectFailed = 11,
|
||||||
|
SocketDisconnecting = 12,
|
||||||
|
SocketBrokenRemotely = 7,
|
||||||
|
}
|
||||||
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronConsole</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronConsole
|
||||||
|
{
|
||||||
|
/// <summary>Prints a line to the Crestron console/telnet output.</summary>
|
||||||
|
void PrintLine(string message);
|
||||||
|
|
||||||
|
/// <summary>Prints text (without newline) to the Crestron console/telnet output.</summary>
|
||||||
|
void Print(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a response string to the console for the currently-executing console command.
|
||||||
|
/// </summary>
|
||||||
|
void ConsoleCommandResponse(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new command with the Crestron console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Handler invoked when the command is typed.</param>
|
||||||
|
/// <param name="command">Command name (no spaces).</param>
|
||||||
|
/// <param name="helpText">Help text shown by the Crestron console.</param>
|
||||||
|
/// <param name="accessLevel">Minimum access level required to run the command.</param>
|
||||||
|
void AddNewConsoleCommand(
|
||||||
|
Action<string> callback,
|
||||||
|
string command,
|
||||||
|
string helpText,
|
||||||
|
ConsoleAccessLevel accessLevel);
|
||||||
|
}
|
||||||
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronDataStore.CrestronDataStoreStatic</c>
|
||||||
|
/// to allow unit testing without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronDataStore
|
||||||
|
{
|
||||||
|
/// <summary>Initialises the data store. Must be called once before any other operation.</summary>
|
||||||
|
void InitStore();
|
||||||
|
|
||||||
|
/// <summary>Reads an integer value from the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||||
|
bool TryGetLocalInt(string key, out int value);
|
||||||
|
|
||||||
|
/// <summary>Writes an integer value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalInt(string key, int value);
|
||||||
|
|
||||||
|
/// <summary>Writes an unsigned integer value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalUint(string key, uint value);
|
||||||
|
|
||||||
|
/// <summary>Reads a boolean value from the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||||
|
bool TryGetLocalBool(string key, out bool value);
|
||||||
|
|
||||||
|
/// <summary>Writes a boolean value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalBool(string key, bool value);
|
||||||
|
}
|
||||||
52
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
52
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronEnvironment</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronEnvironment
|
||||||
|
{
|
||||||
|
/// <summary>Gets the platform the program is executing on.</summary>
|
||||||
|
DevicePlatform DevicePlatform { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the current runtime environment.</summary>
|
||||||
|
RuntimeEnvironment RuntimeEnvironment { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the platform-appropriate newline string.</summary>
|
||||||
|
string NewLine { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the application number (program slot).</summary>
|
||||||
|
uint ApplicationNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the room ID (used in Crestron Virtual Control / server environments).</summary>
|
||||||
|
uint RoomId { get; }
|
||||||
|
|
||||||
|
/// <summary>Raised when program status changes (starting, stopping, etc.).</summary>
|
||||||
|
event EventHandler<ProgramStatusEventArgs> ProgramStatusChanged;
|
||||||
|
|
||||||
|
/// <summary>Raised when the ethernet link changes state.</summary>
|
||||||
|
event EventHandler<PepperDashEthernetEventArgs> EthernetEventReceived;
|
||||||
|
|
||||||
|
/// <summary>Gets the application root directory path.</summary>
|
||||||
|
string GetApplicationRootDirectory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <c>true</c> when running on real Crestron hardware.
|
||||||
|
/// Returns <c>false</c> in test / dev environments so that SDK-specific
|
||||||
|
/// sinks and enrichers can be safely skipped.
|
||||||
|
/// </summary>
|
||||||
|
bool IsHardwareRuntime { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for <see cref="ICrestronEnvironment.ProgramStatusChanged"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ProgramStatusEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public ProgramStatusEventType EventType { get; }
|
||||||
|
|
||||||
|
public ProgramStatusEventArgs(ProgramStatusEventType eventType)
|
||||||
|
{
|
||||||
|
EventType = eventType;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronEthernetHelper</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEthernetHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a network parameter string for the specified adapter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The parameter to retrieve.</param>
|
||||||
|
/// <param name="ethernetAdapterId">Ethernet adapter index (0 = LAN A).</param>
|
||||||
|
/// <returns>String value of the requested parameter.</returns>
|
||||||
|
string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>PepperDash.Core.Abstractions</RootNamespace>
|
||||||
|
<AssemblyName>PepperDash.Core.Abstractions</AssemblyName>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
|
<Title>PepperDash Core Abstractions</Title>
|
||||||
|
<Company>PepperDash Technologies</Company>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://github.com/PepperDash/PepperDashCore</RepositoryUrl>
|
||||||
|
<NullableContextOptions>enable</NullableContextOptions>
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<!-- No Crestron SDK reference — this project must remain hardware-agnostic so test projects can reference it -->
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Devices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for <see cref="Device"/> — the base class for all PepperDash devices.
|
||||||
|
/// These run without Crestron hardware; Debug is initialized with fakes via TestInitializer.
|
||||||
|
/// </summary>
|
||||||
|
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<int>();
|
||||||
|
|
||||||
|
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<int>();
|
||||||
|
|
||||||
|
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
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exposes protected Device members so test methods can call them directly.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records whether CustomActivate was invoked and returns a configured result.
|
||||||
|
/// Used to verify Activate() correctly delegates to CustomActivate().
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No-op ICrestronConsole that captures output for test assertions.
|
||||||
|
/// </summary>
|
||||||
|
public class CapturingCrestronConsole : ICrestronConsole
|
||||||
|
{
|
||||||
|
public List<string> Lines { get; } = new();
|
||||||
|
public List<string> 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<string> callback,
|
||||||
|
string command,
|
||||||
|
string helpText,
|
||||||
|
ConsoleAccessLevel accessLevel)
|
||||||
|
{
|
||||||
|
RegisteredCommands.Add((command, helpText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimal no-op ICrestronConsole that discards all output. Useful when you only
|
||||||
|
/// care about the system under test and not what it logs.
|
||||||
|
/// </summary>
|
||||||
|
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 __, string ___, ConsoleAccessLevel ____) { }
|
||||||
|
}
|
||||||
49
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
49
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configurable ICrestronEnvironment for unit tests.
|
||||||
|
/// Defaults: Appliance / SimplSharpPro / ApplicationNumber=1.
|
||||||
|
/// </summary>
|
||||||
|
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<ProgramStatusEventArgs>? ProgramStatusChanged;
|
||||||
|
public event EventHandler<PepperDashEthernetEventArgs>? EthernetEventReceived;
|
||||||
|
|
||||||
|
public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsHardwareRuntime => false;
|
||||||
|
|
||||||
|
/// <summary>Simulates a program status event for tests.</summary>
|
||||||
|
public void RaiseProgramStatus(ProgramStatusEventType type) =>
|
||||||
|
ProgramStatusChanged?.Invoke(this, new ProgramStatusEventArgs(type));
|
||||||
|
|
||||||
|
/// <summary>Simulates an ethernet event for tests.</summary>
|
||||||
|
public void RaiseEthernetEvent(EthernetEventType type, short adapter = 0) =>
|
||||||
|
EthernetEventReceived?.Invoke(this, new PepperDashEthernetEventArgs(type, adapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No-op IEthernetHelper that returns configurable values.
|
||||||
|
/// </summary>
|
||||||
|
public class FakeEthernetHelper : IEthernetHelper
|
||||||
|
{
|
||||||
|
private readonly Dictionary<EthernetParameterType, string> _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;
|
||||||
|
}
|
||||||
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory ICrestronDataStore backed by a dictionary.
|
||||||
|
/// Use in unit tests to verify that keys are read from and written to the store correctly.
|
||||||
|
/// </summary>
|
||||||
|
public class InMemoryCrestronDataStore : ICrestronDataStore
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object> _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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Seeds a key for testing read paths.</summary>
|
||||||
|
public void Seed(string key, object value) => _store[key] = value;
|
||||||
|
}
|
||||||
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
using PepperDash.Core.Tests.Fakes;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for Debug-related service interfaces and implementations.
|
||||||
|
/// These tests verify the behaviour of the abstractions in isolation (no Crestron SDK required).
|
||||||
|
/// </summary>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
30
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||||
|
<!-- PepperDash.Core is referenced so we can test Device and other concrete types.
|
||||||
|
DebugServiceRegistration.Register() is called via a [ModuleInitializer] before any
|
||||||
|
test runs, so Debug's static constructor uses fakes and never touches the Crestron SDK. -->
|
||||||
|
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
using PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs once before any type in this assembly is accessed.
|
||||||
|
/// Registers fake Crestron service implementations with <see cref="DebugServiceRegistration"/>
|
||||||
|
/// so that the <c>Debug</c> 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 <em>any</em> type in PepperDash.Core is referenced — before xUnit
|
||||||
|
/// has a chance to run fixture setup code.
|
||||||
|
/// </summary>
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PdCore = PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
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<string> 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<string> in a lambda — Crestron's delegate is not a standard Action<string>.
|
||||||
|
CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using Crestron.SimplSharp.CrestronDataStore;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
71
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PdCore = PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
|
||||||
|
{
|
||||||
|
// Subscribe once in constructor and re-raise as our event types.
|
||||||
|
private event EventHandler<PdCore.ProgramStatusEventArgs>? _programStatusChanged;
|
||||||
|
private event EventHandler<PdCore.PepperDashEthernetEventArgs>? _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<PdCore.ProgramStatusEventArgs> ProgramStatusChanged
|
||||||
|
{
|
||||||
|
add => _programStatusChanged += value;
|
||||||
|
remove => _programStatusChanged -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PdCore.PepperDashEthernetEventArgs> EthernetEventReceived
|
||||||
|
{
|
||||||
|
add => _ethernetEventReceived += value;
|
||||||
|
remove => _ethernetEventReceived -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetApplicationRootDirectory() =>
|
||||||
|
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper class for formatting communication text and byte data for debugging purposes.
|
|
||||||
/// </summary>
|
|
||||||
public class ComTextHelper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets escaped text for a byte array
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
/// <returns>string with all bytes escaped</returns>
|
|
||||||
public static string GetEscapedText(byte[] bytes)
|
|
||||||
{
|
|
||||||
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets escaped text for a string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <returns>string with all bytes escaped</returns>
|
|
||||||
public static string GetEscapedText(string text)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
return string.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets debug text for a string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <returns>string with all non-printable characters escaped</returns>
|
|
||||||
public static string GetDebugText(string text)
|
|
||||||
{
|
|
||||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,8 +8,8 @@ using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the string event handler for line events on the gather
|
/// Defines the string event handler for line events on the gather
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -30,7 +30,7 @@ namespace PepperDash.Core
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The communication port that this gathers on
|
/// The communication port that this gathers on
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICommunicationReceiver Port { get; private set; }
|
public ICommunicationReceiver Port { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default false. If true, the delimiter will be included in the line output
|
/// Default false. If true, the delimiter will be included in the line output
|
||||||
|
|
@ -67,22 +67,22 @@ namespace PepperDash.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="delimiter"></param>
|
/// <param name="delimiter"></param>
|
||||||
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
||||||
:this(port, new string[] { delimiter} )
|
:this(port, new string[] { delimiter} )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor for using an array of string delimiters
|
/// Constructor for using an array of string delimiters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="delimiters"></param>
|
/// <param name="delimiters"></param>
|
||||||
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
||||||
{
|
{
|
||||||
Port = port;
|
Port = port;
|
||||||
StringDelimiters = delimiters;
|
StringDelimiters = delimiters;
|
||||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
port.TextReceived += Port_TextReceivedStringDelimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop method
|
/// Stop method
|
||||||
|
|
@ -135,35 +135,35 @@ namespace PepperDash.Core
|
||||||
ReceiveBuffer.Append(args.Text);
|
ReceiveBuffer.Append(args.Text);
|
||||||
var str = ReceiveBuffer.ToString();
|
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
|
// RX: DEV
|
||||||
// Split: (1) "DEV"
|
// Split: (1) "DEV"
|
||||||
// RX: I
|
// RX: I
|
||||||
// Split: (1) "DEVI"
|
// Split: (1) "DEVI"
|
||||||
// RX: CE get version
|
// RX: CE get version
|
||||||
// Split: (1) "DEVICE get version"
|
// Split: (1) "DEVICE get version"
|
||||||
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
|
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
|
||||||
// Split: (2) DEVICE get version, +OK "value":"1234"
|
// Split: (2) DEVICE get version, +OK "value":"1234"
|
||||||
|
|
||||||
// Iterate the delimiters and fire an event for any matching delimiter
|
// Iterate the delimiters and fire an event for any matching delimiter
|
||||||
foreach (var delimiter in StringDelimiters)
|
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++)
|
||||||
{
|
{
|
||||||
var lines = Regex.Split(str, delimiter);
|
string strToSend = null;
|
||||||
if (lines.Length == 1)
|
if (IncludeDelimiter)
|
||||||
continue;
|
strToSend = lines[i] + delimiter;
|
||||||
|
else
|
||||||
for (int i = 0; i < lines.Length - 1; i++)
|
strToSend = lines[i];
|
||||||
{
|
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter));
|
||||||
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,5 +174,4 @@ namespace PepperDash.Core
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,138 +1,159 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using System.Timers;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
||||||
|
/// </summary>
|
||||||
|
public class CommunicationStreamDebugging
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
/// Device Key that this instance configures
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommunicationStreamDebugging
|
public string ParentDeviceKey { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer to disable automatically if not manually disabled
|
||||||
|
/// </summary>
|
||||||
|
private Timer DebugExpiryPeriod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current debug setting
|
||||||
|
/// </summary>
|
||||||
|
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
||||||
|
|
||||||
|
private uint _DebugTimeoutInMs;
|
||||||
|
private const uint _DefaultDebugTimeoutMin = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timeout in Minutes
|
||||||
|
/// </summary>
|
||||||
|
public uint DebugTimeoutMinutes
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Device Key that this instance configures
|
|
||||||
/// </summary>
|
|
||||||
public string ParentDeviceKey { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timer to disable automatically if not manually disabled
|
|
||||||
/// </summary>
|
|
||||||
private CTimer DebugExpiryPeriod;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the DebugSetting
|
|
||||||
/// </summary>
|
|
||||||
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
|
||||||
|
|
||||||
private uint _DebugTimeoutInMs;
|
|
||||||
private const uint _DefaultDebugTimeoutMin = 30;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timeout in Minutes
|
|
||||||
/// </summary>
|
|
||||||
public uint DebugTimeoutMinutes
|
|
||||||
{
|
{
|
||||||
get
|
return _DebugTimeoutInMs/60000;
|
||||||
{
|
|
||||||
return _DebugTimeoutInMs / 60000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the RxStreamDebuggingIsEnabled
|
|
||||||
/// </summary>
|
|
||||||
public bool RxStreamDebuggingIsEnabled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that transmit stream debugging is enabled
|
|
||||||
/// </summary>
|
|
||||||
public bool TxStreamDebuggingIsEnabled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parentDeviceKey"></param>
|
|
||||||
public CommunicationStreamDebugging(string parentDeviceKey)
|
|
||||||
{
|
|
||||||
ParentDeviceKey = parentDeviceKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="setting"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetDebuggingWithDefaultTimeout method
|
|
||||||
/// </summary>
|
|
||||||
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
|
||||||
{
|
|
||||||
if (setting == eStreamDebuggingSetting.Off)
|
|
||||||
{
|
|
||||||
DisableDebugging();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the debugging setting for the specified number of minutes
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="setting"></param>
|
|
||||||
/// <param name="minutes"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetDebuggingWithSpecificTimeout method
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disabled debugging
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that receive stream debugging is enabled
|
||||||
|
/// </summary>
|
||||||
|
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that transmit stream debugging is enabled
|
||||||
|
/// </summary>
|
||||||
|
public bool TxStreamDebuggingIsEnabled { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parentDeviceKey"></param>
|
||||||
|
public CommunicationStreamDebugging(string parentDeviceKey)
|
||||||
|
{
|
||||||
|
ParentDeviceKey = parentDeviceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setting"></param>
|
||||||
|
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
||||||
|
{
|
||||||
|
if (setting == eStreamDebuggingSetting.Off)
|
||||||
|
{
|
||||||
|
DisableDebugging();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the debugging setting for the specified number of minutes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setting"></param>
|
||||||
|
/// <param name="minutes"></param>
|
||||||
|
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
||||||
|
{
|
||||||
|
if (setting == eStreamDebuggingSetting.Off)
|
||||||
|
{
|
||||||
|
DisableDebugging();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disabled debugging
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The available settings for stream debugging
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum eStreamDebuggingSetting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Debug off
|
||||||
|
/// </summary>
|
||||||
|
Off = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug received data
|
||||||
|
/// </summary>
|
||||||
|
Rx = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug transmitted data
|
||||||
|
/// </summary>
|
||||||
|
Tx = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug both received and transmitted data
|
||||||
|
/// </summary>
|
||||||
|
Both = Rx | Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,97 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
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;
|
||||||
|
|
||||||
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Config properties that indicate how to communicate with a device for control
|
||||||
|
/// </summary>
|
||||||
|
public class ControlPropertiesConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a ControlPropertiesConfig
|
/// The method of control
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControlPropertiesConfig
|
[JsonProperty("method")]
|
||||||
{
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
/// <summary>
|
public eControlMethod Method { get; set; }
|
||||||
/// The method of control
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("method")]
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public eControlMethod Method { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key of the device that contains the control port
|
/// The key of the device that contains the control port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string ControlPortDevKey { get; set; }
|
public string ControlPortDevKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of the control port on the device specified by ControlPortDevKey
|
/// The number of the control port on the device specified by ControlPortDevKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||||
public uint? ControlPortNumber { get; set; }
|
public uint? ControlPortNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the control port on the device specified by ControlPortDevKey
|
/// The name of the control port on the device specified by ControlPortDevKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||||
public string ControlPortName { get; set; }
|
public string ControlPortName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Properties for ethernet based communications
|
/// Properties for ethernet based communications
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The filename and path for the IR file
|
/// The filename and path for the IR file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string IrFile { get; set; }
|
public string IrFile { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IpId of a Crestron device
|
/// The IpId of a Crestron device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string IpId { get; set; }
|
public string IpId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Readonly uint representation of the IpId
|
/// Readonly uint representation of the IpId
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Char indicating end of line
|
/// Char indicating end of line
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public char EndOfLineChar { get; set; }
|
public char EndOfLineChar { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to Environment.NewLine;
|
/// Defaults to Environment.NewLine;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string EndOfLineString { get; set; }
|
public string EndOfLineString { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates
|
/// Indicates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string DeviceReadyResponsePattern { get; set; }
|
public string DeviceReadyResponsePattern { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used when communcating to programs running in VC-4
|
/// Used when communcating to programs running in VC-4
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string RoomId { get; set; }
|
public string RoomId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ControlPropertiesConfig()
|
public ControlPropertiesConfig()
|
||||||
{
|
{
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,236 +16,237 @@ using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for notifying of socket status changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EventArgs class for socket status changes
|
||||||
|
/// </summary>
|
||||||
|
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for notifying of socket status changes
|
/// Gets or sets the Client
|
||||||
|
/// </summary>
|
||||||
|
public ISocketStatus Client { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||||
|
{
|
||||||
/// <summary>
|
Client = client;
|
||||||
/// EventArgs class for socket status changes
|
|
||||||
/// </summary>
|
|
||||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Client
|
|
||||||
/// </summary>
|
|
||||||
public ISocketStatus Client { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
|
||||||
{
|
|
||||||
Client = client;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericSocketStatusChageEventArgs() { }
|
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericSocketStatusChageEventArgs() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for notifying of TCP Server state changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EventArgs class for TCP Server state changes
|
||||||
|
/// </summary>
|
||||||
|
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the State
|
||||||
|
/// </summary>
|
||||||
|
public ServerState State { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for notifying of TCP Server state changes
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="state"></param>
|
||||||
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||||
|
{
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericTcpServerStateChangedEventArgs() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for TCP Server socket status changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="socket"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <param name="clientStatus"></param>
|
||||||
|
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
||||||
|
/// <summary>
|
||||||
|
/// EventArgs for TCP server socket status changes
|
||||||
|
/// </summary>
|
||||||
|
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Socket
|
||||||
|
/// </summary>
|
||||||
|
public object Socket { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventArgs class for TCP Server state changes
|
/// Gets or sets the index of the client from which the status change was received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the State
|
|
||||||
/// </summary>
|
|
||||||
public ServerState State { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the ClientStatus
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
public SocketStatus ClientStatus { get; set; }
|
||||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
|
||||||
{
|
/// <summary>
|
||||||
State = state;
|
/// Constructor
|
||||||
}
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="socket"></param>
|
||||||
/// S+ Constructor
|
/// <param name="clientStatus"></param>
|
||||||
/// </summary>
|
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
||||||
public GenericTcpServerStateChangedEventArgs() { }
|
{
|
||||||
|
Socket = socket;
|
||||||
|
ClientStatus = clientStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for TCP Server socket status changes
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="socket"></param>
|
/// <param name="socket"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <param name="clientStatus"></param>
|
/// <param name="clientStatus"></param>
|
||||||
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
||||||
/// <summary>
|
|
||||||
/// EventArgs for TCP server socket status changes
|
|
||||||
/// </summary>
|
|
||||||
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
Socket = socket;
|
||||||
///
|
ReceivedFromClientIndex = clientIndex;
|
||||||
/// </summary>
|
ClientStatus = clientStatus;
|
||||||
public object Socket { get; private set; }
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||||
/// <summary>
|
}
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// EventArgs for TCP server com method receive text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="socket"></param>
|
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||||
/// <param name="clientStatus"></param>
|
{
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
/// <summary>
|
||||||
{
|
/// Gets or sets the index of the client from which the text was received
|
||||||
Socket = socket;
|
/// </summary>
|
||||||
ClientStatus = clientStatus;
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets the index of the client from which the text was received as a ushort
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="socket"></param>
|
public ushort ReceivedFromClientIndexShort
|
||||||
/// <param name="clientIndex"></param>
|
{
|
||||||
/// <param name="clientStatus"></param>
|
get
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
|
||||||
{
|
{
|
||||||
Socket = socket;
|
return (ushort)ReceivedFromClientIndex;
|
||||||
ReceivedFromClientIndex = clientIndex;
|
|
||||||
ClientStatus = clientStatus;
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventArgs for TCP server com method receive text
|
/// Gets or sets the Text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
public string Text { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Text = text;
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort ReceivedFromClientIndexShort
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return (ushort)ReceivedFromClientIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Text
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
|
||||||
{
|
|
||||||
Text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <param name="clientIndex"></param>
|
|
||||||
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
|
||||||
{
|
|
||||||
Text = text;
|
|
||||||
ReceivedFromClientIndex = clientIndex;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventArgs for TCP server client ready for communication
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
/// <param name="text"></param>
|
||||||
|
/// <param name="clientIndex"></param>
|
||||||
|
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Text = text;
|
||||||
///
|
ReceivedFromClientIndex = clientIndex;
|
||||||
/// </summary>
|
}
|
||||||
public bool IsReady;
|
/// <summary>
|
||||||
|
/// S+ Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// EventArgs for TCP server client ready for communication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="isReady"></param>
|
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
{
|
||||||
{
|
/// <summary>
|
||||||
IsReady = isReady;
|
/// Gets or sets IsReady
|
||||||
}
|
/// </summary>
|
||||||
/// <summary>
|
public bool IsReady;
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isReady"></param>
|
||||||
|
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||||
|
{
|
||||||
|
IsReady = isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventArgs for UDP connected
|
/// S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericUdpConnectedEventArgs : EventArgs
|
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EventArgs for UDP connected
|
||||||
|
/// </summary>
|
||||||
|
public class GenericUdpConnectedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the UConnected
|
||||||
|
/// </summary>
|
||||||
|
public ushort UConnected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Connected status
|
||||||
|
/// </summary>
|
||||||
|
public bool Connected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericUdpConnectedEventArgs() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uconnected"></param>
|
||||||
|
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||||
{
|
{
|
||||||
/// <summary>
|
UConnected = uconnected;
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort UConnected;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool Connected;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericUdpConnectedEventArgs() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uconnected"></param>
|
|
||||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
|
||||||
{
|
|
||||||
UConnected = uconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connected"></param>
|
|
||||||
public GenericUdpConnectedEventArgs(bool connected)
|
|
||||||
{
|
|
||||||
Connected = connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connected"></param>
|
||||||
|
public GenericUdpConnectedEventArgs(bool connected)
|
||||||
|
{
|
||||||
|
Connected = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,371 +1,357 @@
|
||||||
|
|
||||||
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using Newtonsoft.Json;
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic UDP Server device
|
||||||
|
/// </summary>
|
||||||
|
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||||
{
|
{
|
||||||
|
private const string SplusKey = "Uninitialized Udp Server";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic UDP Server device
|
/// Object to enable stream debugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||||
{
|
/// <summary>
|
||||||
private const string SplusKey = "Uninitialized Udp Server";
|
///
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Object to enable stream debugging
|
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
/// </summary>
|
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SocketStatus ClientStatus
|
public SocketStatus ClientStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
get
|
return Server.ServerStatus;
|
||||||
{
|
|
||||||
return Server.ServerStatus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)Server.ServerStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
|
||||||
/// which screws up things
|
|
||||||
/// </summary>
|
|
||||||
public ushort UPort
|
|
||||||
{
|
|
||||||
get { return Convert.ToUInt16(Port); }
|
|
||||||
set { Port = Convert.ToInt32(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the UDP Server is enabled
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Numeric value indicating
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return IsConnected ? (ushort)1 : (ushort)0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The server
|
|
||||||
/// </summary>
|
|
||||||
public UDPServer Server { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
|
|
||||||
/// </summary>
|
|
||||||
public GenericUdpServer()
|
|
||||||
: base(SplusKey)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
|
||||||
BufferSize = 5000;
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bufferSize"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call from S+ to initialize values
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize method
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key, string address, ushort port)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
Hostname = address;
|
|
||||||
UPort = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernetEventArgs"></param>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="programEventType"></param>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType != eProgramStatusEventType.Stopping)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect method
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnect method
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
if (Server != null)
|
|
||||||
Server.DisableUDPServer();
|
|
||||||
|
|
||||||
IsConnected = false;
|
|
||||||
|
|
||||||
var handler = UpdateConnectionStatus;
|
|
||||||
if (handler != null)
|
|
||||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Recursive method to receive data
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="server"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SendText method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
|
|
||||||
if (IsConnected && Server != null)
|
|
||||||
{
|
|
||||||
this.PrintSentText(text);
|
|
||||||
|
|
||||||
Server.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SendBytes method
|
|
||||||
/// </summary>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
this.PrintSentBytes(bytes);
|
|
||||||
|
|
||||||
if (IsConnected && Server != null)
|
|
||||||
Server.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
public ushort UStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
get { return (ushort)Server.ServerStatus; }
|
||||||
///
|
}
|
||||||
/// </summary>
|
|
||||||
|
/// <summary>
|
||||||
|
/// Address of server
|
||||||
|
/// </summary>
|
||||||
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port on server
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||||
|
/// which screws up things
|
||||||
|
/// </summary>
|
||||||
|
public ushort UPort
|
||||||
|
{
|
||||||
|
get { return Convert.ToUInt16(Port); }
|
||||||
|
set { Port = Convert.ToInt32(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the UDP Server is enabled
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Numeric value indicating
|
||||||
|
/// </summary>
|
||||||
|
public ushort UIsConnected
|
||||||
|
{
|
||||||
|
get { return IsConnected ? (ushort)1 : (ushort)0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 2000
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server
|
||||||
|
/// </summary>
|
||||||
|
public UDPServer Server { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
|
||||||
|
/// </summary>
|
||||||
|
public GenericUdpServer()
|
||||||
|
: base(SplusKey)
|
||||||
|
{
|
||||||
|
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||||
|
BufferSize = 5000;
|
||||||
|
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
|
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="buffefSize"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call from S+ to initialize values
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
public void Initialize(string key, string address, ushort port)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Hostname = address;
|
||||||
|
UPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ethernetEventArgs"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="programEventType"></param>
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType != eProgramStatusEventType.Stopping)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.LogInformation("Program stopping. Disabling Server");
|
||||||
|
Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the UDP Server
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disabled the UDP Server
|
||||||
|
/// </summary>
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
if(Server != null)
|
||||||
|
Server.DisableUDPServer();
|
||||||
|
|
||||||
|
IsConnected = false;
|
||||||
|
|
||||||
|
var handler = UpdateConnectionStatus;
|
||||||
|
if (handler != null)
|
||||||
|
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursive method to receive data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="numBytes"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// General send method
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string IpAddress { get; private set; }
|
public string IpAddress { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; private set; }
|
public int Port { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] Bytes { get; private set; }
|
public byte[] Bytes { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="ipAddress"></param>
|
/// <param name="ipAddress"></param>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||||
{
|
{
|
||||||
Text = text;
|
Text = text;
|
||||||
|
|
@ -380,34 +366,33 @@ namespace PepperDash.Core
|
||||||
public GenericUdpReceiveTextExtraArgs() { }
|
public GenericUdpReceiveTextExtraArgs() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class UdpServerPropertiesConfig
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UdpServerPropertiesConfig
|
[JsonProperty(Required = Required.Always)]
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(Required = Required.Always)]
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defaults to 32768
|
||||||
|
/// </summary>
|
||||||
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public UdpServerPropertiesConfig()
|
||||||
{
|
{
|
||||||
/// <summary>
|
BufferSize = 32768;
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
|
||||||
public string Address { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 32768
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public UdpServerPropertiesConfig()
|
|
||||||
{
|
|
||||||
BufferSize = 32768;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,59 +1,60 @@
|
||||||
using Newtonsoft.Json;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
|
|
||||||
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
|
||||||
|
/// </summary>
|
||||||
|
public class TcpClientConfigObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a TcpClientConfigObject
|
/// TcpSsh Properties
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpClientConfigObject
|
[JsonProperty("control")]
|
||||||
{
|
public ControlPropertiesConfig Control { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// TcpSsh Properties
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("control")]
|
|
||||||
public ControlPropertiesConfig Control { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("secure")]
|
[JsonProperty("secure")]
|
||||||
public bool Secure { get; set; }
|
public bool Secure { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("sharedKeyRequired")]
|
[JsonProperty("sharedKeyRequired")]
|
||||||
public bool SharedKeyRequired { get; set; }
|
public bool SharedKeyRequired { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The shared key that must match on the server and client
|
/// The shared key that must match on the server and client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("sharedKey")]
|
[JsonProperty("sharedKey")]
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
/// 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.
|
/// heartbeats do not raise received events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("heartbeatRequired")]
|
[JsonProperty("heartbeatRequired")]
|
||||||
public bool HeartbeatRequired { get; set; }
|
public bool HeartbeatRequired { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("heartbeatStringToMatch")]
|
[JsonProperty("heartbeatStringToMatch")]
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("receiveQueueSize")]
|
[JsonProperty("receiveQueueSize")]
|
||||||
public int ReceiveQueueSize { get; set; }
|
public int ReceiveQueueSize { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,57 +4,56 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
||||||
|
/// </summary>
|
||||||
|
public class TcpServerConfigObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
/// Uique key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpServerConfigObject
|
public string Key { get; set; }
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
/// Max Clients that the server will allow to connect.
|
||||||
/// Uique key
|
/// </summary>
|
||||||
/// </summary>
|
public ushort MaxClients { get; set; }
|
||||||
public string Key { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||||
/// Max Clients that the server will allow to connect.
|
/// </summary>
|
||||||
/// </summary>
|
public bool Secure { get; set; }
|
||||||
public ushort MaxClients { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Port for the server to listen on
|
||||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
/// </summary>
|
||||||
/// </summary>
|
public int Port { get; set; }
|
||||||
public bool Secure { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||||
/// Port for the server to listen on
|
/// </summary>
|
||||||
/// </summary>
|
public bool SharedKeyRequired { get; set; }
|
||||||
public int Port { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// The shared key that must match on the server and client
|
||||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
/// </summary>
|
||||||
/// </summary>
|
public string SharedKey { get; set; }
|
||||||
public bool SharedKeyRequired { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
||||||
/// The shared key that must match on the server and client
|
/// heartbeats do not raise received events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public bool HeartbeatRequired { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||||
/// heartbeats do not raise received events.
|
/// </summary>
|
||||||
/// </summary>
|
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||||
public bool HeartbeatRequired { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
/// </summary>
|
||||||
/// </summary>
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
||||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
/// </summary>
|
||||||
/// </summary>
|
public int BufferSize { get; set; }
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
/// <summary>
|
||||||
/// <summary>
|
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||||
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
/// </summary>
|
||||||
/// </summary>
|
public int ReceiveQueueSize { get; set; }
|
||||||
public int BufferSize { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
|
||||||
/// </summary>
|
|
||||||
public int ReceiveQueueSize { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,84 +4,83 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crestron Control Methods for a comm object
|
||||||
|
/// </summary>
|
||||||
|
public enum eControlMethod
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crestron Control Methods for a comm object
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum eControlMethod
|
None = 0,
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
/// RS232/422/485
|
||||||
///
|
/// </summary>
|
||||||
/// </summary>
|
Com,
|
||||||
None = 0,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestron IpId (most Crestron ethernet devices)
|
||||||
/// RS232/422/485
|
/// </summary>
|
||||||
/// </summary>
|
IpId,
|
||||||
Com,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestron IpIdTcp (HD-MD series, etc.)
|
||||||
/// Crestron IpId (most Crestron ethernet devices)
|
/// </summary>
|
||||||
/// </summary>
|
IpidTcp,
|
||||||
IpId,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestron IR control
|
||||||
/// Crestron IpIdTcp (HD-MD series, etc.)
|
/// </summary>
|
||||||
/// </summary>
|
IR,
|
||||||
IpidTcp,
|
/// <summary>
|
||||||
/// <summary>
|
/// SSH client
|
||||||
/// Crestron IR control
|
/// </summary>
|
||||||
/// </summary>
|
Ssh,
|
||||||
IR,
|
/// <summary>
|
||||||
/// <summary>
|
/// TCP/IP client
|
||||||
/// SSH client
|
/// </summary>
|
||||||
/// </summary>
|
Tcpip,
|
||||||
Ssh,
|
/// <summary>
|
||||||
/// <summary>
|
/// Telnet
|
||||||
/// TCP/IP client
|
/// </summary>
|
||||||
/// </summary>
|
Telnet,
|
||||||
Tcpip,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestnet device
|
||||||
/// Telnet
|
/// </summary>
|
||||||
/// </summary>
|
Cresnet,
|
||||||
Telnet,
|
/// <summary>
|
||||||
/// <summary>
|
/// CEC Control, via a DM HDMI port
|
||||||
/// Crestnet device
|
/// </summary>
|
||||||
/// </summary>
|
Cec,
|
||||||
Cresnet,
|
/// <summary>
|
||||||
/// <summary>
|
/// UDP Server
|
||||||
/// CEC Control, via a DM HDMI port
|
/// </summary>
|
||||||
/// </summary>
|
Udp,
|
||||||
Cec,
|
/// <summary>
|
||||||
/// <summary>
|
/// HTTP client
|
||||||
/// UDP Server
|
/// </summary>
|
||||||
/// </summary>
|
Http,
|
||||||
Udp,
|
/// <summary>
|
||||||
/// <summary>
|
/// HTTPS client
|
||||||
/// HTTP client
|
/// </summary>
|
||||||
/// </summary>
|
Https,
|
||||||
Http,
|
/// <summary>
|
||||||
/// <summary>
|
/// Websocket client
|
||||||
/// HTTPS client
|
/// </summary>
|
||||||
/// </summary>
|
Ws,
|
||||||
Https,
|
/// <summary>
|
||||||
/// <summary>
|
/// Secure Websocket client
|
||||||
/// Websocket client
|
/// </summary>
|
||||||
/// </summary>
|
Wss,
|
||||||
Ws,
|
/// <summary>
|
||||||
/// <summary>
|
/// Secure TCP/IP
|
||||||
/// Secure Websocket client
|
/// </summary>
|
||||||
/// </summary>
|
SecureTcpIp,
|
||||||
Wss,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestron COM bridge
|
||||||
/// Secure TCP/IP
|
/// </summary>
|
||||||
/// </summary>
|
ComBridge,
|
||||||
SecureTcpIp,
|
/// <summary>
|
||||||
/// <summary>
|
/// Crestron Infinet EX device
|
||||||
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
|
/// </summary>
|
||||||
/// </summary>
|
InfinetEx
|
||||||
ComBridge,
|
|
||||||
/// <summary>
|
|
||||||
/// InfinetEX control
|
|
||||||
/// </summary>
|
|
||||||
InfinetEx
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The available settings for stream debugging
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum eStreamDebuggingSetting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Debug off
|
|
||||||
/// </summary>
|
|
||||||
Off = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug received data
|
|
||||||
/// </summary>
|
|
||||||
Rx = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug transmitted data
|
|
||||||
/// </summary>
|
|
||||||
Tx = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug both received and transmitted data
|
|
||||||
/// </summary>
|
|
||||||
Both = Rx | Tx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +1,143 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using Newtonsoft.Json;
|
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
|
||||||
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
|
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An incoming communication stream
|
||||||
|
/// </summary>
|
||||||
|
public interface ICommunicationReceiver : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An incoming communication stream
|
/// Notifies of bytes received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICommunicationReceiver : IKeyed
|
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
/// Notifies of text received
|
||||||
/// Notifies of bytes received
|
/// </summary>
|
||||||
/// </summary>
|
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of text received
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates connection status
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isConnected")]
|
|
||||||
bool IsConnected { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// Connect to the device
|
|
||||||
/// </summary>
|
|
||||||
void Connect();
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnect from the device
|
|
||||||
/// </summary>
|
|
||||||
void Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the contract for IBasicCommunication
|
/// Indicates connection status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBasicCommunication : ICommunicationReceiver
|
[JsonProperty("isConnected")]
|
||||||
{
|
bool IsConnected { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send text to the device
|
/// Connect to the device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
void Connect();
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect from the device
|
||||||
|
/// </summary>
|
||||||
|
void Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extends <see cref="ICommunicationReceiver"/> with methods for sending text and bytes to a device.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBasicCommunication : ICommunicationReceiver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send text to the device
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
void SendText(string text);
|
void SendText(string text);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send bytes to the device
|
/// Send bytes to the device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
void SendBytes(byte[] bytes);
|
void SendBytes(byte[] bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
|
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a device with stream debugging capablities
|
||||||
|
/// </summary>
|
||||||
|
public interface IStreamDebugging : IKeyed
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a device with stream debugging capablities
|
/// Object to enable stream debugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStreamDebugging : IKeyed
|
[JsonProperty("streamDebugging")]
|
||||||
{
|
CommunicationStreamDebugging StreamDebugging { get; }
|
||||||
/// <summary>
|
}
|
||||||
/// Object to enable stream debugging
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("streamDebugging")]
|
|
||||||
CommunicationStreamDebugging StreamDebugging { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||||
|
/// GenericTcpIpClient
|
||||||
|
/// </summary>
|
||||||
|
public interface ISocketStatus : IBasicCommunication
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
/// Notifies of socket status changes
|
||||||
/// GenericTcpIpClient
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISocketStatus : IBasicCommunication
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of socket status changes
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current socket status of the client
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("clientStatus")]
|
|
||||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
|
||||||
SocketStatus ClientStatus { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
/// The current socket status of the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
|
[JsonProperty("clientStatus")]
|
||||||
{
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
SocketStatus ClientStatus { get; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
||||||
|
/// </summary>
|
||||||
|
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
|
||||||
|
{
|
||||||
|
|
||||||
/// <summary>
|
}
|
||||||
/// Describes a device that can automatically attempt to reconnect
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
|
/// Describes a device that can automatically attempt to reconnect
|
||||||
|
/// </summary>
|
||||||
public interface IAutoReconnect
|
public interface IAutoReconnect
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Enable automatic recconnect
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("autoReconnect")]
|
|
||||||
bool AutoReconnect { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Interval in ms to attempt automatic recconnections
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("autoReconnectIntervalMs")]
|
|
||||||
int AutoReconnectIntervalMs { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Enable automatic recconnect
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("autoReconnect")]
|
||||||
|
bool AutoReconnect { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Interval in ms to attempt automatic recconnections
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("autoReconnectIntervalMs")]
|
||||||
|
int AutoReconnectIntervalMs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public enum eGenericCommMethodStatusChangeType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum eGenericCommMethodStatusChangeType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Connected
|
|
||||||
/// </summary>
|
|
||||||
Connected,
|
Connected,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnected
|
/// Disconnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disconnected
|
Disconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This delegate defines handler for IBasicCommunication status changes
|
/// This delegate defines handler for IBasicCommunication status changes
|
||||||
|
|
@ -139,20 +146,20 @@ namespace PepperDash.Core
|
||||||
/// <param name="status"></param>
|
/// <param name="status"></param>
|
||||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for bytes received from a communication method
|
||||||
|
/// </summary>
|
||||||
|
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// The bytes received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
public byte[] Bytes { get; private set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Bytes
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Bytes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||||
{
|
{
|
||||||
Bytes = bytes;
|
Bytes = bytes;
|
||||||
|
|
@ -164,42 +171,81 @@ namespace PepperDash.Core
|
||||||
public GenericCommMethodReceiveBytesArgs() { }
|
public GenericCommMethodReceiveBytesArgs() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for text received
|
||||||
|
/// </summary>
|
||||||
|
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// The text received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// The delimiter used to determine the end of a message, if applicable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Delimiter { get; private set; }
|
public string Delimiter { get; private set; }
|
||||||
/// <summary>
|
|
||||||
///
|
/// <summary>
|
||||||
/// </summary>
|
/// Constructor
|
||||||
/// <param name="text"></param>
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
public GenericCommMethodReceiveTextArgs(string text)
|
public GenericCommMethodReceiveTextArgs(string text)
|
||||||
{
|
{
|
||||||
Text = text;
|
Text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="delimiter"></param>
|
/// <param name="delimiter"></param>
|
||||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
||||||
: this(text)
|
:this(text)
|
||||||
{
|
{
|
||||||
Delimiter = delimiter;
|
Delimiter = delimiter;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericCommMethodReceiveTextArgs() { }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// S+ Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericCommMethodReceiveTextArgs() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to get escaped text for debugging communication streams
|
||||||
|
/// </summary>
|
||||||
|
public class ComTextHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets escaped text for a byte array
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetEscapedText(byte[] bytes)
|
||||||
|
{
|
||||||
|
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets escaped text for a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetEscapedText(string text)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
|
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets debug text for a string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetDebugText(string text)
|
||||||
|
{
|
||||||
|
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json;
|
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||||
using Newtonsoft.Json.Linq;
|
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||||
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
|
using JToken = NewtonsoftJson::Newtonsoft.Json.Linq.JToken;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.Config
|
namespace PepperDash.Core.Config;
|
||||||
{
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reads a Portal formatted config file
|
||||||
/// Reads a Portal formatted config file
|
/// </summary>
|
||||||
/// </summary>
|
|
||||||
public class PortalConfigReader
|
public class PortalConfigReader
|
||||||
{
|
{
|
||||||
const string template = "template";
|
const string template = "template";
|
||||||
|
|
@ -56,12 +58,12 @@ namespace PepperDash.Core.Config
|
||||||
var merged = MergeConfigs(jsonObj);
|
var merged = MergeConfigs(jsonObj);
|
||||||
if (jsonObj[systemUrl] != null)
|
if (jsonObj[systemUrl] != null)
|
||||||
{
|
{
|
||||||
merged[systemUrl] = jsonObj[systemUrl].Value<string>();
|
merged[systemUrl] = (string)jsonObj[systemUrl];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonObj[templateUrl] != null)
|
if (jsonObj[templateUrl] != null)
|
||||||
{
|
{
|
||||||
merged[templateUrl] = jsonObj[templateUrl].Value<string>();
|
merged[templateUrl] = (string)jsonObj[templateUrl];
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj = merged;
|
jsonObj = merged;
|
||||||
|
|
@ -122,38 +124,37 @@ namespace PepperDash.Core.Config
|
||||||
Merge(template[destinationLists], system[destinationLists], destinationLists));
|
Merge(template[destinationLists], system[destinationLists], destinationLists));
|
||||||
|
|
||||||
|
|
||||||
if (system[cameraLists] == null)
|
if (system["cameraLists"] == null)
|
||||||
merged.Add(cameraLists, template[cameraLists]);
|
merged.Add("cameraLists", template["cameraLists"]);
|
||||||
else
|
else
|
||||||
merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists));
|
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
|
||||||
|
|
||||||
if (system[audioControlPointLists] == null)
|
if (system["audioControlPointLists"] == null)
|
||||||
merged.Add(audioControlPointLists, template[audioControlPointLists]);
|
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
|
||||||
else
|
else
|
||||||
merged.Add(audioControlPointLists,
|
merged.Add("audioControlPointLists",
|
||||||
Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists));
|
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
|
||||||
|
|
||||||
|
|
||||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
// Template tie lines take precedence. Config tool doesn't do them at system
|
||||||
// level anyway...
|
// level anyway...
|
||||||
if (template[tieLines] != null)
|
if (template["tieLines"] != null)
|
||||||
merged.Add(tieLines, template[tieLines]);
|
merged.Add("tieLines", template["tieLines"]);
|
||||||
else if (system[tieLines] != null)
|
else if (system["tieLines"] != null)
|
||||||
merged.Add(tieLines, system[tieLines]);
|
merged.Add("tieLines", system["tieLines"]);
|
||||||
else
|
else
|
||||||
merged.Add(tieLines, new JArray());
|
merged.Add(tieLines, new JArray());
|
||||||
|
|
||||||
if (template[joinMaps] != null)
|
if (template["joinMaps"] != null)
|
||||||
merged.Add(joinMaps, template[joinMaps]);
|
merged.Add("joinMaps", template["joinMaps"]);
|
||||||
else
|
else
|
||||||
merged.Add(joinMaps, new JObject());
|
merged.Add("joinMaps", new JObject());
|
||||||
|
|
||||||
if (system[global] != null)
|
if (system[global] != null)
|
||||||
merged.Add(global, Merge(template[global], system[global], global));
|
merged.Add(global, Merge(template[global], system[global], global));
|
||||||
else
|
else
|
||||||
merged.Add(global, template[global]);
|
merged.Add(global, template[global]);
|
||||||
|
|
||||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,26 +170,26 @@ namespace PepperDash.Core.Config
|
||||||
return a1;
|
return a1;
|
||||||
else if (a1 != null)
|
else if (a1 != null)
|
||||||
{
|
{
|
||||||
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
|
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
|
||||||
{ // with the system array
|
{ // with the system array
|
||||||
return a2;
|
return a2;
|
||||||
}
|
}
|
||||||
else // The arrays are keyed, merge them by key
|
else // The arrays are keyed, merge them by key
|
||||||
|
{
|
||||||
|
for (int i = 0; i < a1.Count(); i++)
|
||||||
{
|
{
|
||||||
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<int>("uid") == tmplDev.Value<int>("uid"));
|
||||||
|
if (a2Match != null)
|
||||||
{
|
{
|
||||||
var a1Dev = a1[i];
|
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
|
||||||
// Try to get a system device and if found, merge it onto template
|
result.Add(mergedItem);
|
||||||
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("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);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
result.Add(a1Dev);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -205,9 +206,9 @@ namespace PepperDash.Core.Config
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Merge o2 onto o1
|
/// Merge o2 onto o1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="o1"></param>
|
/// <param name="o1"></param>
|
||||||
/// <param name="o2"></param>
|
/// <param name="o2"></param>
|
||||||
/// <param name="path"></param>
|
/// <param name="path"></param>
|
||||||
static JObject Merge(JObject o1, JObject o2, string path)
|
static JObject Merge(JObject o1, JObject o2, string path)
|
||||||
{
|
{
|
||||||
foreach (var o2Prop in o2)
|
foreach (var o2Prop in o2)
|
||||||
|
|
@ -253,5 +254,4 @@ namespace PepperDash.Core.Config
|
||||||
}
|
}
|
||||||
return o1;
|
return o1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,28 +4,18 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
public class EncodingHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static string ConvertUtf8ToAscii(string utf8String)
|
||||||
/// Represents a EncodingHelper
|
|
||||||
/// </summary>
|
|
||||||
public class EncodingHelper
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||||
/// ConvertUtf8ToAscii method
|
|
||||||
/// </summary>
|
|
||||||
public static string ConvertUtf8ToAscii(string utf8String)
|
|
||||||
{
|
|
||||||
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ConvertUtf8ToUtf16 method
|
|
||||||
/// </summary>
|
|
||||||
public static string ConvertUtf8ToUtf16(string utf8String)
|
|
||||||
{
|
|
||||||
return Encoding.Unicode.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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,35 +1,35 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Newtonsoft.Json;
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique key interface to require a unique key for the class
|
/// Unique key interface to require a unique key for the class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IKeyed
|
public interface IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique Key
|
/// Gets the unique key associated with the object.
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("key")]
|
|
||||||
string Key { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Named Keyed device interface. Forces the device to have a Unique Key and a name.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IKeyName : IKeyed
|
[JsonProperty("key")]
|
||||||
{
|
string Key { get; }
|
||||||
/// <summary>
|
}
|
||||||
/// Isn't it obvious :)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
|
||||||
string Name { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Named Keyed device interface. Forces the device to have a Unique Key and a name.
|
||||||
|
/// </summary>
|
||||||
|
public interface IKeyName : IKeyed
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name associated with the current object.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("name")]
|
||||||
|
string Name { get; }
|
||||||
}
|
}
|
||||||
|
|
@ -2,198 +2,183 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
//*********************************************************************************************************
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Device
|
||||||
|
/// </summary>
|
||||||
|
public class Device : IKeyName
|
||||||
{
|
{
|
||||||
//*********************************************************************************************************
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Device
|
/// Unique Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Device : IKeyName
|
public string Key { get; protected set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; protected set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; protected set; }
|
||||||
|
|
||||||
|
List<Action> _PreActivationActions;
|
||||||
|
List<Action> _PostActivationActions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
||||||
|
static Device _DefaultDevice = new Device("Default", "Default");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base constructor for all Devices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public Device(string key)
|
||||||
{
|
{
|
||||||
|
Key = key;
|
||||||
|
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
|
||||||
|
Name = "";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique Key
|
/// Constructor with key and name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; protected set; }
|
/// <param name="key"></param>
|
||||||
/// <summary>
|
/// <param name="name"></param>
|
||||||
/// Gets or sets the Name
|
public Device(string key, string name) : this(key)
|
||||||
/// </summary>
|
{
|
||||||
public string Name { get; protected set; }
|
Name = name;
|
||||||
/// <summary>
|
}
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool Enabled { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
//public Device(DeviceConfig config)
|
||||||
/// A place to store reference to the original config object, if any. These values should
|
// : this(config.Key, config.Name)
|
||||||
/// NOT be used as properties on the device as they are all publicly-settable values.
|
//{
|
||||||
/// </summary>
|
// Config = config;
|
||||||
//public DeviceConfig Config { get; private set; }
|
//}
|
||||||
/// <summary>
|
|
||||||
/// Helper method to check if Config exists
|
|
||||||
/// </summary>
|
|
||||||
//public bool HasConfig { get { return Config != null; } }
|
|
||||||
|
|
||||||
List<Action> _PreActivationActions;
|
/// <summary>
|
||||||
List<Action> _PostActivationActions;
|
/// Adds a pre activation action
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="act"></param>
|
||||||
|
protected void AddPreActivationAction(Action act)
|
||||||
|
{
|
||||||
|
if (_PreActivationActions == null)
|
||||||
|
_PreActivationActions = new List<Action>();
|
||||||
|
_PreActivationActions.Add(act);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Adds a post activation action
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
/// <param name="act"></param>
|
||||||
static Device _DefaultDevice = new Device("Default", "Default");
|
/// <summary>
|
||||||
|
/// AddPostActivationAction method
|
||||||
|
/// </summary>
|
||||||
|
protected void AddPostActivationAction(Action act)
|
||||||
|
{
|
||||||
|
if (_PostActivationActions == null)
|
||||||
|
_PostActivationActions = new List<Action>();
|
||||||
|
_PostActivationActions.Add(act);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base constructor for all Devices.
|
/// PreActivate method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
public void PreActivate()
|
||||||
public Device(string key)
|
{
|
||||||
{
|
if (_PreActivationActions != null)
|
||||||
Key = key;
|
_PreActivationActions.ForEach(a =>
|
||||||
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
|
{
|
||||||
Name = "";
|
try
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor with key and name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="name"></param>
|
|
||||||
public Device(string key, string name) : this(key)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//public Device(DeviceConfig config)
|
|
||||||
// : this(config.Key, config.Name)
|
|
||||||
//{
|
|
||||||
// Config = config;
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a pre activation action
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="act"></param>
|
|
||||||
public void AddPreActivationAction(Action act)
|
|
||||||
{
|
|
||||||
if (_PreActivationActions == null)
|
|
||||||
_PreActivationActions = new List<Action>();
|
|
||||||
_PreActivationActions.Add(act);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a post activation action
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="act"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// AddPostActivationAction method
|
|
||||||
/// </summary>
|
|
||||||
public void AddPostActivationAction(Action act)
|
|
||||||
{
|
|
||||||
if (_PostActivationActions == null)
|
|
||||||
_PostActivationActions = new List<Action>();
|
|
||||||
_PostActivationActions.Add(act);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PreActivate method
|
|
||||||
/// </summary>
|
|
||||||
public void PreActivate()
|
|
||||||
{
|
|
||||||
if (_PreActivationActions != null)
|
|
||||||
_PreActivationActions.ForEach(a =>
|
|
||||||
{
|
{
|
||||||
try
|
a.Invoke();
|
||||||
{
|
}
|
||||||
a.Invoke();
|
catch (Exception e)
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Activate method
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PostActivate method
|
|
||||||
/// </summary>
|
|
||||||
public void PostActivate()
|
|
||||||
{
|
|
||||||
if (_PostActivationActions != null)
|
|
||||||
_PostActivationActions.ForEach(a =>
|
|
||||||
{
|
{
|
||||||
try
|
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||||
{
|
}
|
||||||
a.Invoke();
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called in between Pre and PostActivationActions when Activate() is called.
|
/// Activate method
|
||||||
/// Override to provide addtitional setup when calling activation. Overriding classes
|
/// </summary>
|
||||||
/// do not need to call base.CustomActivate()
|
public bool Activate()
|
||||||
/// </summary>
|
{
|
||||||
/// <returns>true if device activated successfully.</returns>
|
//if (_PreActivationActions != null)
|
||||||
/// <summary>
|
// _PreActivationActions.ForEach(a => a.Invoke());
|
||||||
/// CustomActivate method
|
var result = CustomActivate();
|
||||||
/// </summary>
|
//if(result && _PostActivationActions != null)
|
||||||
public virtual bool CustomActivate() { return true; }
|
// _PostActivationActions.ForEach(a => a.Invoke());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call to deactivate device - unlink events, etc. Overriding classes do not
|
/// PostActivate method
|
||||||
/// need to call base.Deactivate()
|
/// </summary>
|
||||||
/// </summary>
|
public void PostActivate()
|
||||||
/// <returns></returns>
|
{
|
||||||
public virtual bool Deactivate() { return true; }
|
if (_PostActivationActions != null)
|
||||||
|
_PostActivationActions.ForEach(a =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
a.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
/// Called in between Pre and PostActivationActions when Activate() is called.
|
||||||
/// </summary>
|
/// Override to provide addtitional setup when calling activation. Overriding classes
|
||||||
public virtual void Initialize()
|
/// do not need to call base.CustomActivate()
|
||||||
{
|
/// </summary>
|
||||||
}
|
/// <returns>true if device activated successfully.</returns>
|
||||||
|
/// <summary>
|
||||||
|
/// CustomActivate method
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool CustomActivate() { return true; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method to check object for bool value false and fire an Action method
|
/// Call to deactivate device - unlink events, etc. Overriding classes do not
|
||||||
/// </summary>
|
/// need to call base.Deactivate()
|
||||||
/// <param name="o">Should be of type bool, others will be ignored</param>
|
/// </summary>
|
||||||
/// <param name="a">Action to be run when o is false</param>
|
/// <returns></returns>
|
||||||
public void OnFalse(object o, Action a)
|
public virtual bool Deactivate() { return true; }
|
||||||
{
|
|
||||||
if (o is bool && !(bool)o) a();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a string representation of the object, including its key and name.
|
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
protected virtual void Initialize()
|
||||||
/// null or empty, "---" is used in place of the name.</remarks>
|
{
|
||||||
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
}
|
||||||
/// <summary>
|
|
||||||
/// ToString method
|
/// <summary>
|
||||||
/// </summary>
|
/// Helper method to check object for bool value false and fire an Action method
|
||||||
public override string ToString()
|
/// </summary>
|
||||||
{
|
/// <param name="o">Should be of type bool, others will be ignored</param>
|
||||||
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
/// <param name="a">Action to be run when o is false</param>
|
||||||
}
|
public void OnFalse(object o, Action a)
|
||||||
|
{
|
||||||
|
if (o is bool && !(bool)o) a();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string representation of the object, including its key and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
||||||
|
/// null or empty, "---" is used in place of the name.</remarks>
|
||||||
|
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
using Crestron.SimplSharp;
|
extern alias NewtonsoftJson;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a EthernetHelper
|
/// Represents an EthernetHelper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EthernetHelper
|
public class EthernetHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -113,5 +115,4 @@ namespace PepperDash.Core
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0);
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool change event args
|
/// Bool change event args
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -168,5 +168,4 @@ namespace PepperDash.Core
|
||||||
Type = type;
|
Type = type;
|
||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.GenericRESTfulCommunications
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants
|
|
||||||
/// </summary>
|
|
||||||
public class GenericRESTfulConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic boolean change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort BoolValueChange = 1;
|
|
||||||
/// <summary>
|
|
||||||
/// Generic Ushort change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort UshrtValueChange = 101;
|
|
||||||
/// <summary>
|
|
||||||
/// Response Code Ushort change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ResponseCodeChange = 102;
|
|
||||||
/// <summary>
|
|
||||||
/// Generic String chagne
|
|
||||||
/// </summary>
|
|
||||||
public const ushort StringValueChange = 201;
|
|
||||||
/// <summary>
|
|
||||||
/// Response string change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ResponseStringChange = 202;
|
|
||||||
/// <summary>
|
|
||||||
/// Error string change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ErrorStringChange = 203;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic RESTful communication class
|
|
||||||
/// </summary>
|
|
||||||
public class GenericRESTfulClient
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Boolean event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
|
||||||
/// <summary>
|
|
||||||
/// String event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericRESTfulClient()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generic RESTful submit request
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="requestType"></param>
|
|
||||||
/// <param name="username"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
/// <param name="contentType"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private HTTP submit request
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="requestType"></param>
|
|
||||||
/// <param name="contentType"></param>
|
|
||||||
/// <param name="username"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private HTTPS submit request
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="requestType"></param>
|
|
||||||
/// <param name="contentType"></param>
|
|
||||||
/// <param name="username"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private method to encode username and password to Base64 string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="username"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
/// <returns>authorization</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected method to handle boolean change events
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected mehtod to handle ushort change events
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected method to handle string change events
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
namespace PepperDash.Core.JsonStandardObjects;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants for simpl modules
|
/// Constants for simpl modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -73,5 +73,4 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
Type = type;
|
Type = type;
|
||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using Crestron.SimplSharp;
|
||||||
using PepperDash.Core.JsonToSimpl;
|
using PepperDash.Core.JsonToSimpl;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
namespace PepperDash.Core.JsonStandardObjects;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Device class
|
/// Device class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -182,5 +182,4 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion EventHandler Helpers
|
#endregion EventHandler Helpers
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
namespace PepperDash.Core.JsonStandardObjects;
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Convert JSON snippt to C#: http://json2csharp.com/#
|
Convert JSON snippt to C#: http://json2csharp.com/#
|
||||||
|
|
||||||
|
|
@ -52,55 +52,55 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ComParamsConfig
|
public class ComParamsConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the baudRate
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int baudRate { get; set; }
|
public int baudRate { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int dataBits { get; set; }
|
public int dataBits { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int stopBits { get; set; }
|
public int stopBits { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string parity { get; set; }
|
public string parity { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string protocol { get; set; }
|
public string protocol { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string hardwareHandshake { get; set; }
|
public string hardwareHandshake { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string softwareHandshake { get; set; }
|
public string softwareHandshake { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int pacing { get; set; }
|
public int pacing { get; set; }
|
||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplBaudRate
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
|
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplDataBits
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
|
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } }
|
public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplPacing { get { return Convert.ToUInt16(pacing); } }
|
public ushort simplPacing { get { return Convert.ToUInt16(pacing); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -117,43 +117,43 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpSshPropertiesConfig
|
public class TcpSshPropertiesConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string address { get; set; }
|
public string address { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int port { get; set; }
|
public int port { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string username { get; set; }
|
public string username { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string password { get; set; }
|
public string password { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool autoReconnect { get; set; }
|
public bool autoReconnect { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int autoReconnectIntervalMs { get; set; }
|
public int autoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplPort
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplPort { get { return Convert.ToUInt16(port); } }
|
public ushort simplPort { get { return Convert.ToUInt16(port); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplAutoReconnect
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
|
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } }
|
public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -170,31 +170,31 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControlConfig
|
public class ControlConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string method { get; set; }
|
public string method { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string controlPortDevKey { get; set; }
|
public string controlPortDevKey { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int controlPortNumber { get; set; }
|
public int controlPortNumber { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ComParamsConfig comParams { get; set; }
|
public ComParamsConfig comParams { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TcpSshPropertiesConfig tcpSshProperties { get; set; }
|
public TcpSshPropertiesConfig tcpSshProperties { get; set; }
|
||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplControlPortNumber
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
|
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -212,27 +212,27 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PropertiesConfig
|
public class PropertiesConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int deviceId { get; set; }
|
public int deviceId { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool enabled { get; set; }
|
public bool enabled { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ControlConfig control { get; set; }
|
public ControlConfig control { get; set; }
|
||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplDeviceId
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
|
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the simplEnabled
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
|
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -249,9 +249,8 @@ namespace PepperDash.Core.JsonStandardObjects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RootObject
|
public class RootObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The collection of devices
|
/// The collection of devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<DeviceConfig> devices { get; set; }
|
public List<DeviceConfig> devices { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,78 +4,78 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants for Simpl modules
|
/// Constants for Simpl modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplConstants
|
public class JsonToSimplConstants
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort BoolValueChange = 1;
|
public const ushort BoolValueChange = 1;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort JsonIsValidBoolChange = 2;
|
public const ushort JsonIsValidBoolChange = 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the if the device is 3-series compatible
|
/// Reports the if the device is 3-series compatible
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProgramCompatibility3SeriesChange = 3;
|
public const ushort ProgramCompatibility3SeriesChange = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the if the device is 4-series compatible
|
/// Reports the if the device is 4-series compatible
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProgramCompatibility4SeriesChange = 4;
|
public const ushort ProgramCompatibility4SeriesChange = 4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the device platform enum value
|
/// Reports the device platform enum value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort DevicePlatformValueChange = 5;
|
public const ushort DevicePlatformValueChange = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort UshortValueChange = 101;
|
public const ushort UshortValueChange = 101;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort StringValueChange = 201;
|
public const ushort StringValueChange = 201;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort FullPathToArrayChange = 202;
|
public const ushort FullPathToArrayChange = 202;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ActualFilePathChange = 203;
|
public const ushort ActualFilePathChange = 203;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort FilenameResolvedChange = 204;
|
public const ushort FilenameResolvedChange = 204;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort FilePathResolvedChange = 205;
|
public const ushort FilePathResolvedChange = 205;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the root directory change
|
/// Reports the root directory change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort RootDirectoryChange = 206;
|
public const ushort RootDirectoryChange = 206;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the room ID change
|
/// Reports the room ID change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort RoomIdChange = 207;
|
public const ushort RoomIdChange = 207;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the room name change
|
/// Reports the room name change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort RoomNameChange = 208;
|
public const ushort RoomNameChange = 208;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -88,33 +88,33 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SPlusValueWrapper
|
public class SPlusValueWrapper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ValueType
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SPlusType ValueType { get; private set; }
|
public SPlusType ValueType { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Index { get; private set; }
|
public ushort Index { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort BoolUShortValue { get; set; }
|
public ushort BoolUShortValue { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string StringValue { get; set; }
|
public string StringValue { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SPlusValueWrapper() {}
|
public SPlusValueWrapper() {}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
public SPlusValueWrapper(SPlusType type, ushort index)
|
public SPlusValueWrapper(SPlusType type, ushort index)
|
||||||
{
|
{
|
||||||
ValueType = type;
|
ValueType = type;
|
||||||
|
|
@ -127,17 +127,16 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SPlusType
|
public enum SPlusType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Digital
|
/// Digital
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Digital,
|
Digital,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Analog
|
/// Analog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Analog,
|
Analog,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// String
|
/// String
|
||||||
/// </summary>
|
/// </summary>
|
||||||
String
|
String
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -7,11 +7,11 @@ using Serilog.Events;
|
||||||
|
|
||||||
//using PepperDash.Core;
|
//using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The global class to manage all the instances of JsonToSimplMaster
|
/// The global class to manage all the instances of JsonToSimplMaster
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class J2SGlobal
|
public class J2SGlobal
|
||||||
{
|
{
|
||||||
static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>();
|
static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>();
|
||||||
|
|
@ -22,10 +22,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// master, this will fail
|
/// master, this will fail
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="master">New master to add</param>
|
/// <param name="master">New master to add</param>
|
||||||
///
|
///
|
||||||
/// <summary>
|
|
||||||
/// AddMaster method
|
|
||||||
/// </summary>
|
|
||||||
public static void AddMaster(JsonToSimplMaster master)
|
public static void AddMaster(JsonToSimplMaster master)
|
||||||
{
|
{
|
||||||
if (master == null)
|
if (master == null)
|
||||||
|
|
@ -59,5 +56,4 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
{
|
{
|
||||||
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json.Linq;
|
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a JsonToSimplArrayLookupChild
|
/// Used to interact with an array of values with the S+ modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
|
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SearchPropertyName { get; set; }
|
public string SearchPropertyName { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SearchPropertyValue { get; set; }
|
public string SearchPropertyValue { get; set; }
|
||||||
|
|
||||||
int ArrayIndex;
|
int ArrayIndex;
|
||||||
|
|
@ -76,10 +78,9 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
PathSuffix == null ? "" : PathSuffix);
|
PathSuffix == null ? "" : PathSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ProcessAll method
|
/// Process all values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <inheritdoc />
|
|
||||||
public override void ProcessAll()
|
public override void ProcessAll()
|
||||||
{
|
{
|
||||||
if (FindInArray())
|
if (FindInArray())
|
||||||
|
|
@ -129,7 +130,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
var item = array.FirstOrDefault(o =>
|
var item = array.FirstOrDefault(o =>
|
||||||
{
|
{
|
||||||
var prop = o[SearchPropertyName];
|
var prop = o[SearchPropertyName];
|
||||||
return prop != null && prop.Value<string>()
|
return prop != null && ((string)prop)
|
||||||
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
||||||
});
|
});
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
|
@ -159,5 +160,4 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,31 +1,34 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json.Linq;
|
using PepperDash.Core.Logging;
|
||||||
|
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for JSON objects
|
/// Base class for JSON objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class JsonToSimplChildObjectBase : IKeyed
|
public abstract class JsonToSimplChildObjectBase : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of bool change
|
/// Notifies of bool change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of ushort change
|
/// Notifies of ushort change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<UshrtChangeEventArgs> UShortChange;
|
public event EventHandler<UshrtChangeEventArgs> UShortChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of string change
|
/// Notifies of string change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate to get all values
|
/// Delegate to get all values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
|
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -33,9 +36,9 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Key
|
/// Unique identifier for instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; protected set; }
|
public string Key { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -49,33 +52,33 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PathSuffix { get; protected set; }
|
public string PathSuffix { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the LinkedToObject
|
/// Indicates if the instance is linked to an object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LinkedToObject { get; protected set; }
|
public bool LinkedToObject { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reference to Master instance
|
/// Reference to Master instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected JsonToSimplMaster Master;
|
protected JsonToSimplMaster Master;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Paths to boolean values in JSON structure
|
/// Paths to boolean values in JSON structure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>();
|
protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Paths to numeric values in JSON structure
|
/// Paths to numeric values in JSON structure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>();
|
protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Paths to string values in JSON structure
|
/// Paths to string values in JSON structure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>();
|
protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call this before doing anything else
|
/// Call this before doing anything else
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="masterUniqueId"></param>
|
/// <param name="masterUniqueId"></param>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="pathPrefix"></param>
|
/// <param name="pathPrefix"></param>
|
||||||
/// <param name="pathSuffix"></param>
|
/// <param name="pathSuffix"></param>
|
||||||
|
|
@ -89,16 +92,13 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
if (Master != null)
|
if (Master != null)
|
||||||
Master.AddChild(this);
|
Master.AddChild(this);
|
||||||
else
|
else
|
||||||
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
this.LogWarning("JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the path prefix for the object
|
/// Sets the path prefix for the object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pathPrefix"></param>
|
/// <param name="pathPrefix"></param>
|
||||||
/// <summary>
|
|
||||||
/// SetPathPrefix method
|
|
||||||
/// </summary>
|
|
||||||
public void SetPathPrefix(string pathPrefix)
|
public void SetPathPrefix(string pathPrefix)
|
||||||
{
|
{
|
||||||
PathPrefix = pathPrefix;
|
PathPrefix = pathPrefix;
|
||||||
|
|
@ -108,7 +108,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetBoolPath(ushort index, string path)
|
public void SetBoolPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
this.LogDebug("JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
BoolPaths[index] = path;
|
BoolPaths[index] = path;
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +118,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetUshortPath(ushort index, string path)
|
public void SetUshortPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
this.LogDebug("JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
UshortPaths[index] = path;
|
UshortPaths[index] = path;
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetStringPath(ushort index, string path)
|
public void SetStringPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
this.LogDebug("JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
StringPaths[index] = path;
|
StringPaths[index] = path;
|
||||||
}
|
}
|
||||||
|
|
@ -141,13 +141,13 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
{
|
{
|
||||||
if (!LinkedToObject)
|
if (!LinkedToObject)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
this.LogDebug("Not linked to object in file. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
if (SetAllPathsDelegate == null)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
|
this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetAllPathsDelegate();
|
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
|
// Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event
|
||||||
void ProcessUshortPath(ushort index) {
|
void ProcessUshortPath(ushort index) {
|
||||||
string response;
|
string response;
|
||||||
if (Process(UshortPaths[index], out response)) {
|
if (Process(UshortPaths[index], out response)) {
|
||||||
ushort val;
|
ushort val;
|
||||||
try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); }
|
try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); }
|
||||||
catch { val = 0; }
|
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.
|
// Processes the path to a string property and fires of a StringChange event.
|
||||||
void ProcessStringPath(ushort index)
|
void ProcessStringPath(ushort index)
|
||||||
|
|
@ -207,11 +207,11 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
bool Process(string path, out string response)
|
bool Process(string path, out string response)
|
||||||
{
|
{
|
||||||
path = GetFullPath(path);
|
path = GetFullPath(path);
|
||||||
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
|
this.LogDebug("JSON Child[{0}] Processing {1}", Key, path);
|
||||||
response = "";
|
response = "";
|
||||||
if (Master == null)
|
if (Master == null)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
|
this.LogWarning("JSONChild[{0}] cannot process without Master attached", Key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,8 +233,8 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
if (isCount)
|
if (isCount)
|
||||||
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
||||||
else
|
else
|
||||||
response = t.Value<string>();
|
response = (string)t;
|
||||||
Debug.Console(1, " ='{0}'", response);
|
this.LogDebug(" ='{0}'", response);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,13 +260,13 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
{
|
{
|
||||||
if (!LinkedToObject)
|
if (!LinkedToObject)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
this.LogDebug("Not linked to object in file. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
if (SetAllPathsDelegate == null)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetAllPathsDelegate();
|
SetAllPathsDelegate();
|
||||||
|
|
@ -275,75 +275,60 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
GetAllValuesDelegate();
|
GetAllValuesDelegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
/// <summary>
|
|
||||||
/// USetBoolValue method
|
|
||||||
/// </summary>
|
|
||||||
public void USetBoolValue(ushort key, ushort theValue)
|
public void USetBoolValue(ushort key, ushort theValue)
|
||||||
{
|
{
|
||||||
SetBoolValue(key, theValue == 1);
|
SetBoolValue(key, theValue == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
/// <summary>
|
|
||||||
/// SetBoolValue method
|
|
||||||
/// </summary>
|
|
||||||
public void SetBoolValue(ushort key, bool theValue)
|
public void SetBoolValue(ushort key, bool theValue)
|
||||||
{
|
{
|
||||||
if (BoolPaths.ContainsKey(key))
|
if (BoolPaths.ContainsKey(key))
|
||||||
SetValueOnMaster(BoolPaths[key], new JValue(theValue));
|
SetValueOnMaster(BoolPaths[key], new JValue(theValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
/// <summary>
|
|
||||||
/// SetUShortValue method
|
|
||||||
/// </summary>
|
|
||||||
public void SetUShortValue(ushort key, ushort theValue)
|
public void SetUShortValue(ushort key, ushort theValue)
|
||||||
{
|
{
|
||||||
if (UshortPaths.ContainsKey(key))
|
if (UshortPaths.ContainsKey(key))
|
||||||
SetValueOnMaster(UshortPaths[key], new JValue(theValue));
|
SetValueOnMaster(UshortPaths[key], new JValue(theValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
/// <summary>
|
|
||||||
/// SetStringValue method
|
|
||||||
/// </summary>
|
|
||||||
public void SetStringValue(ushort key, string theValue)
|
public void SetStringValue(ushort key, string theValue)
|
||||||
{
|
{
|
||||||
if (StringPaths.ContainsKey(key))
|
if (StringPaths.ContainsKey(key))
|
||||||
SetValueOnMaster(StringPaths[key], new JValue(theValue));
|
SetValueOnMaster(StringPaths[key], new JValue(theValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="keyPath"></param>
|
/// <param name="keyPath"></param>
|
||||||
/// <param name="valueToSave"></param>
|
/// <param name="valueToSave"></param>
|
||||||
/// <summary>
|
|
||||||
/// SetValueOnMaster method
|
|
||||||
/// </summary>
|
|
||||||
public void SetValueOnMaster(string keyPath, JValue valueToSave)
|
public void SetValueOnMaster(string keyPath, JValue valueToSave)
|
||||||
{
|
{
|
||||||
var path = GetFullPath(keyPath);
|
var path = GetFullPath(keyPath);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
this.LogDebug("JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
||||||
|
|
||||||
//var token = Master.JsonObject.SelectToken(path);
|
//var token = Master.JsonObject.SelectToken(path);
|
||||||
//if (token != null) // The path exists in the file
|
//if (token != null) // The path exists in the file
|
||||||
|
|
@ -351,7 +336,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
this.LogDebug("JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,12 +352,12 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
|
|
||||||
// Helpers for events
|
// Helpers for events
|
||||||
//******************************************************************************************
|
//******************************************************************************************
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event helper
|
/// Event helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="state"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||||
{
|
{
|
||||||
var handler = BoolChange;
|
var handler = BoolChange;
|
||||||
|
|
@ -385,12 +370,12 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
|
|
||||||
//******************************************************************************************
|
//******************************************************************************************
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event helper
|
/// Event helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="state"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnUShortChange(ushort state, ushort index, ushort type)
|
protected void OnUShortChange(ushort state, ushort index, ushort type)
|
||||||
{
|
{
|
||||||
var handler = UShortChange;
|
var handler = UShortChange;
|
||||||
|
|
@ -402,12 +387,12 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event helper
|
/// Event helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnStringChange(string value, ushort index, ushort type)
|
protected void OnStringChange(string value, ushort index, ushort type)
|
||||||
{
|
{
|
||||||
var handler = StringChange;
|
var handler = StringChange;
|
||||||
|
|
@ -418,5 +403,4 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
StringChange(this, args);
|
StringChange(this, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,292 +1,263 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json.Linq;
|
using PepperDash.Core.Logging;
|
||||||
|
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||||
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
|
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a JSON file that can be read and written to
|
||||||
|
/// </summary>
|
||||||
|
public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a JSON file that can be read and written to
|
/// Sets the filepath as well as registers this with the Global.Masters list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplFileMaster : JsonToSimplMaster
|
public string Filepath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filepath to the actual file that will be read (Portal or local)
|
||||||
|
/// </summary>
|
||||||
|
public string ActualFilePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string Filename { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
|
||||||
|
/*****************************************************************************************/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SIMPL+ default constructor.
|
||||||
|
/// </summary>
|
||||||
|
public JsonToSimplFileMaster()
|
||||||
{
|
{
|
||||||
/// <summary>
|
}
|
||||||
/// Sets the filepath as well as registers this with the Global.Masters list
|
|
||||||
/// </summary>
|
|
||||||
public string Filepath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ActualFilePath
|
/// Read, evaluate and udpate status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ActualFilePath { get; private set; }
|
public void EvaluateFile(string filepath)
|
||||||
|
{
|
||||||
/// <summary>
|
try
|
||||||
/// Gets or sets the Filename
|
|
||||||
/// </summary>
|
|
||||||
public string Filename { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
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();
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SIMPL+ default constructor.
|
|
||||||
/// </summary>
|
|
||||||
public JsonToSimplFileMaster()
|
|
||||||
{
|
{
|
||||||
}
|
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
||||||
|
|
||||||
/// <summary>
|
var dirSeparator = Path.DirectorySeparatorChar;
|
||||||
/// Read, evaluate and udpate status
|
var dirSeparatorAlt = Path.AltDirectorySeparatorChar;
|
||||||
/// </summary>
|
|
||||||
public void EvaluateFile(string filepath)
|
var series = CrestronEnvironment.ProgramCompatibility;
|
||||||
{
|
|
||||||
try
|
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))
|
||||||
{
|
{
|
||||||
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange);
|
||||||
|
}
|
||||||
|
|
||||||
var dirSeparator = Path.DirectorySeparatorChar;
|
// get the roomName
|
||||||
var dirSeparatorAlt = Path.AltDirectorySeparatorChar;
|
var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName;
|
||||||
|
if (!string.IsNullOrEmpty(roomName))
|
||||||
|
{
|
||||||
|
OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange);
|
||||||
|
}
|
||||||
|
|
||||||
var series = CrestronEnvironment.ProgramCompatibility;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3));
|
filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator);
|
||||||
OnBoolChange(is3Series, 0,
|
|
||||||
JsonToSimplConstants.ProgramCompatibility3SeriesChange);
|
Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory,
|
||||||
|
filepath.TrimStart(dirSeparator, dirSeparatorAlt));
|
||||||
|
|
||||||
|
OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange);
|
||||||
|
|
||||||
var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4));
|
if (string.IsNullOrEmpty(Filepath))
|
||||||
OnBoolChange(is4Series, 0,
|
{
|
||||||
JsonToSimplConstants.ProgramCompatibility4SeriesChange);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server;
|
// get file directory and name to search
|
||||||
OnBoolChange(isServer, 0,
|
var fileDirectory = Path.GetDirectoryName(Filepath);
|
||||||
JsonToSimplConstants.DevicePlatformValueChange);
|
var fileName = Path.GetFileName(Filepath);
|
||||||
|
|
||||||
// get the roomID
|
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
||||||
var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId;
|
this.LogInformation("Checking '{0}' for '{1}'", fileDirectory, fileName);
|
||||||
if (!string.IsNullOrEmpty(roomId))
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange);
|
var msg = string.Format("JSON file not found: {0}", Filepath);
|
||||||
}
|
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
||||||
|
CrestronConsole.PrintLine(msg);
|
||||||
// get the roomName
|
this.LogError(msg);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get file directory and name to search
|
// \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
|
||||||
var fileDirectory = Path.GetDirectoryName(Filepath);
|
// \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
|
||||||
var fileName = Path.GetFileName(Filepath);
|
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);
|
||||||
|
|
||||||
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
Filename = actualFile.Name;
|
||||||
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
|
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
|
||||||
|
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
|
||||||
if (Directory.Exists(fileDirectory))
|
this.LogInformation("JSON Filename is {0}", Filename);
|
||||||
{
|
|
||||||
// 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);
|
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
|
||||||
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
||||||
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
||||||
Debug.Console(1, "JSON File Path is {0}", FilePathName);
|
this.LogInformation("JSON File Path is {0}", FilePathName);
|
||||||
|
|
||||||
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||||
|
|
||||||
JsonObject = JObject.Parse(json);
|
JsonObject = JObject.Parse(json);
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
child.ProcessAll();
|
child.ProcessAll();
|
||||||
|
|
||||||
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "'{0}' not found", fileDirectory);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
else
|
||||||
{
|
{
|
||||||
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
|
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
||||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
this.LogError("'{0}' not found", fileDirectory);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the debug level
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// setDebugLevel method
|
|
||||||
/// </summary>
|
|
||||||
public void setDebugLevel(uint level)
|
|
||||||
{
|
{
|
||||||
Debug.SetDebugLevel(level);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the debug level
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
public void setDebugLevel(uint level)
|
||||||
|
{
|
||||||
|
Debug.SetDebugLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the values to the file
|
||||||
|
/// </summary>
|
||||||
|
public override void Save()
|
||||||
|
{
|
||||||
|
// this code is duplicated in the other masters!!!!!!!!!!!!!
|
||||||
|
UnsavedValues = new Dictionary<string, JValue>();
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
/// Saves the values to the file
|
|
||||||
/// </summary>
|
|
||||||
public override void Save()
|
|
||||||
{
|
{
|
||||||
// this code is duplicated in the other masters!!!!!!!!!!!!!
|
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||||
UnsavedValues = new Dictionary<string, JValue>();
|
return;
|
||||||
// Make each child update their values into master object
|
}
|
||||||
foreach (var child in Children)
|
lock (FileLock)
|
||||||
|
{
|
||||||
|
this.LogInformation("Saving");
|
||||||
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
child.UpdateInputsForMaster();
|
if (tokenToReplace != null)
|
||||||
}
|
{// It's found
|
||||||
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||||
{
|
|
||||||
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lock (FileLock)
|
|
||||||
{
|
|
||||||
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]);
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
try
|
//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);
|
||||||
sw.Write(JsonObject.ToString());
|
|
||||||
sw.Flush();
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
||||||
{
|
{
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
try
|
||||||
Debug.Console(0, err);
|
{
|
||||||
ErrorLog.Warn(err);
|
sw.Write(JsonObject.ToString());
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a JsonToSimplFixedPathObject
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonToSimplFixedPathObject()
|
public JsonToSimplFixedPathObject()
|
||||||
{
|
{
|
||||||
this.LinkedToObject = true;
|
this.LinkedToObject = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,37 +1,42 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Newtonsoft.Json.Linq;
|
using PepperDash.Core.Logging;
|
||||||
|
using Renci.SshNet.Messages;
|
||||||
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
|
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a JsonToSimplGenericMaster
|
/// Generic Master
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplGenericMaster : JsonToSimplMaster
|
public class JsonToSimplGenericMaster : JsonToSimplMaster
|
||||||
{
|
{
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
/** Privates **/
|
/** Privates **/
|
||||||
|
|
||||||
|
|
||||||
// The JSON file in JObject form
|
// The JSON file in JObject form
|
||||||
// For gathering the incoming data
|
// For gathering the incoming data
|
||||||
object StringBuilderLock = new object();
|
object StringBuilderLock = new object();
|
||||||
// To prevent multiple same-file access
|
// To prevent multiple same-file access
|
||||||
static object WriteLock = new object();
|
static object WriteLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SaveCallback
|
/// Callback action for saving
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<string> SaveCallback { get; set; }
|
public Action<string> SaveCallback { get; set; }
|
||||||
|
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SIMPL+ default constructor.
|
/// SIMPL+ default constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonToSimplGenericMaster()
|
public JsonToSimplGenericMaster()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -71,7 +76,8 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
|
this.LogException(e, "JSON parsing failed:\r{0}", e.Message);
|
||||||
|
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,41 +88,40 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
public override void Save()
|
public override void Save()
|
||||||
{
|
{
|
||||||
// this code is duplicated in the other masters!!!!!!!!!!!!!
|
// this code is duplicated in the other masters!!!!!!!!!!!!!
|
||||||
UnsavedValues = new Dictionary<string, JValue>();
|
UnsavedValues = new Dictionary<string, JValue>();
|
||||||
// Make each child update their values into master object
|
// Make each child update their values into master object
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
|
this.LogDebug("Master. checking child [{0}] for updates to save", child.Key);
|
||||||
child.UpdateInputsForMaster();
|
child.UpdateInputsForMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Master. No updated values to save. Skipping");
|
this.LogDebug("Master. No updated values to save. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (WriteLock)
|
lock (WriteLock)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Saving");
|
this.LogDebug("Saving");
|
||||||
foreach (var path in UnsavedValues.Keys)
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
if (tokenToReplace != null)
|
if (tokenToReplace != null)
|
||||||
{// It's found
|
{// It's found
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
Debug.Console(1, this, "Master Updating '{0}'", path);
|
this.LogDebug("Master Updating '{0}'", path);
|
||||||
}
|
}
|
||||||
else // No token. Let's make one
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
|
this.LogDebug("Master Cannot write value onto missing property: '{0}'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (SaveCallback != null)
|
if (SaveCallback != null)
|
||||||
SaveCallback(JsonObject.ToString());
|
SaveCallback(JsonObject.ToString());
|
||||||
else
|
else
|
||||||
Debug.Console(0, this, "WARNING: No save callback defined.");
|
this.LogDebug("WARNING: No save callback defined.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,34 +1,40 @@
|
||||||
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json;
|
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||||
using Newtonsoft.Json.Linq;
|
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;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstract base class for JsonToSimpl interactions
|
/// Abstract base class for JsonToSimpl interactions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class JsonToSimplMaster : IKeyed
|
public abstract class JsonToSimplMaster : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of bool change
|
/// Notifies of bool change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of ushort change
|
/// Notifies of ushort change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of string change
|
/// Notifies of string change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of associated child modules
|
/// A collection of associated child modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>();
|
protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>();
|
||||||
|
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
|
|
@ -38,9 +44,9 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get { return UniqueID; } }
|
public string Key { get { return UniqueID; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the UniqueID
|
/// A unique ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UniqueID { get; protected set; }
|
public string UniqueID { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -82,9 +88,9 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the JsonObject
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JObject JsonObject { get; protected set; }
|
public JObject JsonObject { get; protected set; }
|
||||||
|
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
|
|
@ -137,16 +143,15 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
{
|
{
|
||||||
if (UnsavedValues.ContainsKey(path))
|
if (UnsavedValues.ContainsKey(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);
|
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);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
UnsavedValues.Add(path, value);
|
UnsavedValues.Add(path, value);
|
||||||
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the file
|
/// Saves the file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Save();
|
public abstract void Save();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -155,18 +160,14 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JsonFixes
|
public static class JsonFixes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserializes a string into a JObject
|
/// Deserializes a string into a JObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="json"></param>
|
/// <param name="json"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static JObject ParseObject(string json)
|
public static JObject ParseObject(string json)
|
||||||
{
|
{
|
||||||
#if NET6_0
|
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||||
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 startDepth = reader.Depth;
|
||||||
var obj = JObject.Load(reader);
|
var obj = JObject.Load(reader);
|
||||||
|
|
@ -176,21 +177,15 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserializes a string into a JArray
|
/// Deserializes a string into a JArray
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="json"></param>
|
/// <param name="json"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <summary>
|
|
||||||
/// ParseArray method
|
|
||||||
/// </summary>
|
|
||||||
public static JArray ParseArray(string json)
|
public static JArray ParseArray(string json)
|
||||||
{
|
{
|
||||||
#if NET6_0
|
|
||||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
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 startDepth = reader.Depth;
|
||||||
var obj = JArray.Load(reader);
|
var obj = JArray.Load(reader);
|
||||||
|
|
@ -233,12 +228,12 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper event
|
/// Helper event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnStringChange(string value, ushort index, ushort type)
|
protected void OnStringChange(string value, ushort index, ushort type)
|
||||||
{
|
{
|
||||||
if (StringChange != null)
|
if (StringChange != null)
|
||||||
|
|
@ -249,4 +244,3 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json.Linq;
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
|
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||||
using PepperDash.Core.Config;
|
using PepperDash.Core.Config;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
namespace PepperDash.Core.JsonToSimpl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Portal File Master
|
||||||
|
/// </summary>
|
||||||
|
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Portal File Master
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the filepath as well as registers this with the Global.Masters list
|
/// Sets the filepath as well as registers this with the Global.Masters list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PortalFilepath { get; private set; }
|
public string PortalFilepath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ActualFilePath
|
/// File path of the actual file being read (Portal or local)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ActualFilePath { get; private set; }
|
public string ActualFilePath { get; private set; }
|
||||||
|
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
/** Privates **/
|
/** Privates **/
|
||||||
|
|
@ -33,10 +37,10 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
/*****************************************************************************************/
|
/*****************************************************************************************/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SIMPL+ default constructor.
|
/// SIMPL+ default constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonToSimplPortalFileMaster()
|
public JsonToSimplPortalFileMaster()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -58,19 +62,19 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
// If the portal file is xyz.json, then
|
// If the portal file is xyz.json, then
|
||||||
// the file we want to check for first will be called xyz.local.json
|
// the file we want to check for first will be called xyz.local.json
|
||||||
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
||||||
Debug.Console(0, this, "Checking for local file {0}", localFilepath);
|
this.LogInformation("Checking for local file {0}", localFilepath);
|
||||||
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
||||||
|
|
||||||
if (actualLocalFile != null)
|
if (actualLocalFile != null)
|
||||||
{
|
{
|
||||||
ActualFilePath = actualLocalFile.FullName;
|
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
|
// If the local file does not exist, then read the portal file xyz.json
|
||||||
// and create the local.
|
// and create the local.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
this.LogInformation("Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
||||||
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
||||||
if (actualPortalFile != null)
|
if (actualPortalFile != null)
|
||||||
{
|
{
|
||||||
|
|
@ -78,19 +82,18 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
// got the portal file, hand off to the merge / save method
|
// got the portal file, hand off to the merge / save method
|
||||||
PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath);
|
PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath);
|
||||||
ActualFilePath = newLocalPath;
|
ActualFilePath = newLocalPath;
|
||||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
||||||
Debug.Console(1, this, msg);
|
this.LogError(msg);
|
||||||
ErrorLog.Error(msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we should have a local file. Do it.
|
// At this point we should have a local file. Do it.
|
||||||
Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
|
this.LogInformation("Reading local JSON file {0}", ActualFilePath);
|
||||||
|
|
||||||
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||||
|
|
||||||
|
|
@ -104,8 +107,7 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
||||||
CrestronConsole.PrintLine(msg);
|
this.LogError(msg);
|
||||||
ErrorLog.Error(msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,30 +148,30 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
// Make each child update their values into master object
|
// Make each child update their values into master object
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||||
child.UpdateInputsForMaster();
|
child.UpdateInputsForMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lock (FileLock)
|
lock (FileLock)
|
||||||
{
|
{
|
||||||
Debug.Console(1, "Saving");
|
this.LogInformation("Saving");
|
||||||
foreach (var path in UnsavedValues.Keys)
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
if (tokenToReplace != null)
|
if (tokenToReplace != null)
|
||||||
{// It's found
|
{// It's found
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||||
}
|
}
|
||||||
else // No token. Let's make one
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
//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);
|
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,12 +185,11 @@ namespace PepperDash.Core.JsonToSimpl
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
string err = string.Format("Error writing JSON file:\r{0}", e);
|
||||||
Debug.Console(0, err);
|
this.LogException(e, "Error writing JSON file: {0}", e.Message);
|
||||||
ErrorLog.Warn(err);
|
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,37 +7,38 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
namespace PepperDash.Core.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enriches log events with Crestron-specific context properties, such as the application name based on the device platform.
|
||||||
|
/// </summary>
|
||||||
|
public class CrestronEnricher : ILogEventEnricher
|
||||||
{
|
{
|
||||||
/// <summary>
|
static readonly string _appName;
|
||||||
/// Represents a CrestronEnricher
|
|
||||||
/// </summary>
|
static CrestronEnricher()
|
||||||
public class CrestronEnricher : ILogEventEnricher
|
|
||||||
{
|
{
|
||||||
static readonly string _appName;
|
switch (CrestronEnvironment.DevicePlatform)
|
||||||
|
|
||||||
static CrestronEnricher()
|
|
||||||
{
|
{
|
||||||
switch (CrestronEnvironment.DevicePlatform)
|
case eDevicePlatform.Appliance:
|
||||||
{
|
_appName = $"App {InitialParametersClass.ApplicationNumber}";
|
||||||
case eDevicePlatform.Appliance:
|
break;
|
||||||
_appName = $"App {InitialParametersClass.ApplicationNumber}";
|
case eDevicePlatform.Server:
|
||||||
break;
|
_appName = $"{InitialParametersClass.RoomId}";
|
||||||
case eDevicePlatform.Server:
|
break;
|
||||||
_appName = $"{InitialParametersClass.RoomId}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enrich method
|
|
||||||
/// </summary>
|
|
||||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
|
||||||
{
|
|
||||||
var property = propertyFactory.CreateProperty("App", _appName);
|
|
||||||
|
|
||||||
logEvent.AddOrUpdateProperty(property);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enriches the log event with Crestron-specific properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logEvent"></param>
|
||||||
|
/// <param name="propertyFactory"></param>
|
||||||
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
var property = propertyFactory.CreateProperty("App", _appName);
|
||||||
|
|
||||||
|
logEvent.AddOrUpdateProperty(property);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -9,56 +9,45 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
public class DebugConsoleSink : ILogEventSink
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly ITextFormatter _textFormatter;
|
||||||
/// Represents a DebugConsoleSink
|
|
||||||
/// </summary>
|
public void Emit(LogEvent logEvent)
|
||||||
public class DebugConsoleSink : ILogEventSink
|
|
||||||
{
|
{
|
||||||
private readonly ITextFormatter _textFormatter;
|
if (!Debug.IsRunningOnAppliance) return;
|
||||||
|
|
||||||
/// <summary>
|
/*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
|
||||||
/// Emit method
|
|
||||||
/// </summary>
|
if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
{
|
{
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}";
|
||||||
|
}*/
|
||||||
|
|
||||||
/*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
|
var buffer = new StringWriter(new StringBuilder(256));
|
||||||
|
|
||||||
if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
_textFormatter.Format(logEvent, buffer);
|
||||||
{
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var buffer = new StringWriter(new StringBuilder(256));
|
var message = buffer.ToString();
|
||||||
|
|
||||||
_textFormatter.Format(logEvent, buffer);
|
|
||||||
|
|
||||||
var message = buffer.ToString();
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugConsoleSink(ITextFormatter formatProvider )
|
|
||||||
{
|
|
||||||
_textFormatter = formatProvider ?? new JsonFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DebugConsoleSinkExtensions
|
public DebugConsoleSink(ITextFormatter formatProvider )
|
||||||
{
|
{
|
||||||
/// <summary>
|
_textFormatter = formatProvider ?? new JsonFormatter();
|
||||||
/// DebugConsoleSink method
|
|
||||||
/// </summary>
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a debugging context
|
|
||||||
/// </summary>
|
|
||||||
public class DebugContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the file containing the current debug settings.
|
|
||||||
/// </summary>
|
|
||||||
//string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber);
|
|
||||||
|
|
||||||
DebugContextSaveData SaveData;
|
|
||||||
|
|
||||||
int SaveTimeoutMs = 30000;
|
|
||||||
|
|
||||||
CTimer SaveTimer;
|
|
||||||
|
|
||||||
|
|
||||||
static List<DebugContext> Contexts = new List<DebugContext>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates or gets a debug context
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// GetDebugContext method
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Do not use. For S+ access.
|
|
||||||
/// </summary>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to save memory when shutting down
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="programEventType"></param>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
|
||||||
{
|
|
||||||
if (SaveTimer != null)
|
|
||||||
{
|
|
||||||
SaveTimer.Stop();
|
|
||||||
SaveTimer = null;
|
|
||||||
}
|
|
||||||
Console(0, "Saving debug settings");
|
|
||||||
SaveMemory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for console command
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="levelString"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetDebugFromConsole method
|
|
||||||
/// </summary>
|
|
||||||
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]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the debug level
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetDebugLevel method
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prints message to console if current debug level is equal to or higher than the level of this message.
|
|
||||||
/// Uses CrestronConsole.PrintLine.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <param name="format">Console format string</param>
|
|
||||||
/// <param name="items">Object parameters</param>
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Console method
|
|
||||||
/// </summary>
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <param name="dev"></param>
|
|
||||||
/// <param name="errorLogLevel"></param>
|
|
||||||
/// <param name="format"></param>
|
|
||||||
/// <param name="items"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <param name="errorLogLevel"></param>
|
|
||||||
/// <param name="format"></param>
|
|
||||||
/// <param name="items"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="errorLogLevel"></param>
|
|
||||||
/// <param name="str"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// LogError method
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the memory object after timeout
|
|
||||||
/// </summary>
|
|
||||||
void SaveMemoryOnTimeout()
|
|
||||||
{
|
|
||||||
if (SaveTimer == null)
|
|
||||||
SaveTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
SaveTimer = null;
|
|
||||||
SaveMemory();
|
|
||||||
}, SaveTimeoutMs);
|
|
||||||
else
|
|
||||||
SaveTimer.Reset(SaveTimeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the memory - use SaveMemoryOnTimeout
|
|
||||||
/// </summary>
|
|
||||||
void SaveMemory()
|
|
||||||
{
|
|
||||||
using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(SaveData);
|
|
||||||
sw.Write(json);
|
|
||||||
sw.Flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
void LoadMemory()
|
|
||||||
{
|
|
||||||
var file = GetMemoryFileName();
|
|
||||||
if (File.Exists(file))
|
|
||||||
{
|
|
||||||
using (StreamReader sr = new StreamReader(file))
|
|
||||||
{
|
|
||||||
var data = JsonConvert.DeserializeObject<DebugContextSaveData>(sr.ReadToEnd());
|
|
||||||
if (data != null)
|
|
||||||
{
|
|
||||||
SaveData = data;
|
|
||||||
Debug.Console(1, "Debug memory restored from file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
SaveData = new DebugContextSaveData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to get the file path for this app's debug memory
|
|
||||||
/// </summary>
|
|
||||||
string GetMemoryFileName()
|
|
||||||
{
|
|
||||||
return string.Format(@"\NVRAM\debugSettings\program{0}-{1}", InitialParametersClass.ApplicationNumber, Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class DebugContextSaveData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int Level { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,33 +3,26 @@ using Crestron.SimplSharp.CrestronLogger;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
namespace PepperDash.Core.Logging;
|
||||||
|
|
||||||
|
public class DebugCrestronLoggerSink : ILogEventSink
|
||||||
{
|
{
|
||||||
/// <summary>
|
public void Emit(LogEvent logEvent)
|
||||||
/// Represents a DebugCrestronLoggerSink
|
|
||||||
/// </summary>
|
|
||||||
public class DebugCrestronLoggerSink : ILogEventSink
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (!Debug.IsRunningOnAppliance) return;
|
||||||
/// Emit method
|
|
||||||
/// </summary>
|
string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
|
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
||||||
{
|
{
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}";
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugCrestronLoggerSink()
|
CrestronLogger.WriteToLog(message, (uint)logEvent.Level);
|
||||||
{
|
}
|
||||||
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
|
|
||||||
}
|
public DebugCrestronLoggerSink()
|
||||||
|
{
|
||||||
|
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,63 +9,56 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
namespace PepperDash.Core.Logging;
|
||||||
|
|
||||||
|
public class DebugErrorLogSink : ILogEventSink
|
||||||
{
|
{
|
||||||
/// <summary>
|
private ITextFormatter _formatter;
|
||||||
/// Represents a DebugErrorLogSink
|
|
||||||
/// </summary>
|
private Dictionary<LogEventLevel, Action<string>> _errorLogMap = new Dictionary<LogEventLevel, Action<string>>
|
||||||
public class DebugErrorLogSink : ILogEventSink
|
|
||||||
{
|
{
|
||||||
private ITextFormatter _formatter;
|
{ 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 Dictionary<LogEventLevel, Action<string>> _errorLogMap = new Dictionary<LogEventLevel, Action<string>>
|
if (_formatter == null)
|
||||||
{
|
{
|
||||||
{ LogEventLevel.Verbose, (msg) => ErrorLog.Notice(msg) },
|
var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
||||||
{LogEventLevel.Debug, (msg) => ErrorLog.Notice(msg) },
|
? $"App {InitialParametersClass.ApplicationNumber}"
|
||||||
{LogEventLevel.Information, (msg) => ErrorLog.Notice(msg) },
|
: $"Room {InitialParametersClass.RoomId}";
|
||||||
{LogEventLevel.Warning, (msg) => ErrorLog.Warn(msg) },
|
|
||||||
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
|
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}";
|
||||||
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
|
|
||||||
};
|
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
||||||
/// <summary>
|
{
|
||||||
/// Emit method
|
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}";
|
||||||
/// </summary>
|
}
|
||||||
public void Emit(LogEvent logEvent)
|
} else
|
||||||
{
|
{
|
||||||
string message;
|
var buffer = new StringWriter(new StringBuilder(256));
|
||||||
|
|
||||||
if (_formatter == null)
|
_formatter.Format(logEvent, buffer);
|
||||||
{
|
|
||||||
var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
|
||||||
? $"App {InitialParametersClass.ApplicationNumber}"
|
|
||||||
: $"Room {InitialParametersClass.RoomId}";
|
|
||||||
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}";
|
message = buffer.ToString();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebugErrorLogSink(ITextFormatter formatter = null)
|
if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler))
|
||||||
{
|
{
|
||||||
_formatter = formatter;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebugErrorLogSink(ITextFormatter formatter = null)
|
||||||
|
{
|
||||||
|
_formatter = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,112 +2,72 @@
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Log = PepperDash.Core.Debug;
|
using Log = PepperDash.Core.Debug;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
namespace PepperDash.Core.Logging;
|
||||||
|
|
||||||
|
public static class DebugExtensions
|
||||||
{
|
{
|
||||||
public static class DebugExtensions
|
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Log.LogMessage(ex, message, device, args);
|
||||||
/// LogException method
|
}
|
||||||
/// </summary>
|
|
||||||
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(ex, message, device: device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogVerbose method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
|
||||||
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogVerbose(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogVerbose(this IKeyed device, string message, params object[] args)
|
||||||
/// LogVerbose method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Verbose, device, message, args);
|
||||||
public static void LogVerbose(this IKeyed device, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogVerbose(device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogDebug method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Debug, ex, message, device, args);
|
||||||
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogDebug(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogDebug(this IKeyed device, string message, params object[] args)
|
||||||
/// LogDebug method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Debug, device, message, args);
|
||||||
public static void LogDebug(this IKeyed device, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogDebug(device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogInformation method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Information, ex, message, device, args);
|
||||||
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogInformation(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogInformation(this IKeyed device, string message, params object[] args)
|
||||||
/// LogInformation method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Information, device, message, args);
|
||||||
public static void LogInformation(this IKeyed device, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogInformation(device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogWarning method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Warning, ex, message, device, args);
|
||||||
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogWarning(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogWarning(this IKeyed device, string message, params object[] args)
|
||||||
/// LogWarning method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Warning, device, message, args);
|
||||||
public static void LogWarning(this IKeyed device, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogWarning(device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogError method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Error, ex, message, device, args);
|
||||||
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogError(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogError(this IKeyed device, string message, params object[] args)
|
||||||
/// LogError method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Error, device, message, args);
|
||||||
public static void LogError(this IKeyed device, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogError(device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
/// LogFatal method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
|
||||||
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
|
}
|
||||||
{
|
|
||||||
Log.LogFatal(ex, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
||||||
/// LogFatal method
|
{
|
||||||
/// </summary>
|
Log.LogMessage(LogEventLevel.Fatal, device, message, args);
|
||||||
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogFatal(device, message, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,127 +1,116 @@
|
||||||
using System.Collections.Generic;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Newtonsoft.Json;
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
namespace PepperDash.Core.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class to persist current Debug settings across program restarts
|
||||||
|
/// </summary>
|
||||||
|
public class DebugContextCollection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a DebugContextCollection
|
/// To prevent threading issues with the DeviceDebugSettings collection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugContextCollection
|
private readonly object _deviceDebugSettingsLock = new();
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// To prevent threading issues with the DeviceDebugSettings collection
|
|
||||||
/// </summary>
|
|
||||||
private readonly CCriticalSection _deviceDebugSettingsLock;
|
|
||||||
|
|
||||||
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
|
[JsonProperty("items")]
|
||||||
|
private readonly Dictionary<string, DebugContextItem> _items = new Dictionary<string, DebugContextItem>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection of the debug settings for each device where the dictionary key is the device key
|
/// Collection of the debug settings for each device where the dictionary key is the device key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("deviceDebugSettings")]
|
[JsonProperty("deviceDebugSettings")]
|
||||||
private Dictionary<string, object> DeviceDebugSettings { get; set; }
|
private Dictionary<string, object> DeviceDebugSettings { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DebugContextCollection()
|
public DebugContextCollection()
|
||||||
{
|
{
|
||||||
_deviceDebugSettingsLock = new CCriticalSection();
|
|
||||||
DeviceDebugSettings = new Dictionary<string, object>();
|
|
||||||
_items = new Dictionary<string, DebugContextItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
}
|
||||||
/// Sets the level of a given context item, and adds that item if it does not
|
|
||||||
/// exist
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="contextKey"></param>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetLevel method
|
|
||||||
/// </summary>
|
|
||||||
public void SetLevel(string contextKey, int level)
|
|
||||||
{
|
|
||||||
if (level < 0 || level > 2)
|
|
||||||
return;
|
|
||||||
GetOrCreateItem(contextKey).Level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a level or creates it if not existing
|
/// Sets the level of a given context item, and adds that item if it does not
|
||||||
/// </summary>
|
/// exist
|
||||||
/// <param name="contextKey"></param>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="contextKey"></param>
|
||||||
/// <summary>
|
/// <param name="level"></param>
|
||||||
/// GetOrCreateItem method
|
/// <summary>
|
||||||
/// </summary>
|
/// SetLevel method
|
||||||
public DebugContextItem GetOrCreateItem(string contextKey)
|
/// </summary>
|
||||||
{
|
public void SetLevel(string contextKey, int level)
|
||||||
if (!_items.ContainsKey(contextKey))
|
{
|
||||||
_items[contextKey] = new DebugContextItem { Level = 0 };
|
if (level < 0 || level > 2)
|
||||||
return _items[contextKey];
|
return;
|
||||||
}
|
GetOrCreateItem(contextKey).Level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a level or creates it if not existing
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contextKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetOrCreateItem method
|
||||||
|
/// </summary>
|
||||||
|
public DebugContextItem GetOrCreateItem(string contextKey)
|
||||||
|
{
|
||||||
|
if (!_items.ContainsKey(contextKey))
|
||||||
|
_items[contextKey] = new DebugContextItem { Level = 0 };
|
||||||
|
return _items[contextKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// sets the settings for a device or creates a new entry
|
/// sets the settings for a device or creates a new entry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey"></param>
|
||||||
/// <param name="settings"></param>
|
/// <param name="settings"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <summary>
|
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
||||||
/// SetDebugSettingsForKey method
|
{
|
||||||
/// </summary>
|
lock (_deviceDebugSettingsLock)
|
||||||
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
|
||||||
{
|
{
|
||||||
try
|
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
||||||
{
|
{
|
||||||
_deviceDebugSettingsLock.Enter();
|
DeviceDebugSettings[deviceKey] = settings;
|
||||||
|
|
||||||
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
|
||||||
{
|
|
||||||
DeviceDebugSettings[deviceKey] = settings;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
DeviceDebugSettings.Add(deviceKey, settings);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_deviceDebugSettingsLock.Leave();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
DeviceDebugSettings.Add(deviceKey, settings);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the device settings for a device by key or returns null
|
/// Gets the device settings for a device by key or returns null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <summary>
|
public object GetDebugSettingsForKey(string deviceKey)
|
||||||
/// GetDebugSettingsForKey method
|
{
|
||||||
/// </summary>
|
return DeviceDebugSettings[deviceKey];
|
||||||
public object GetDebugSettingsForKey(string deviceKey)
|
}
|
||||||
{
|
}
|
||||||
return DeviceDebugSettings[deviceKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains information about
|
/// Contains information about
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugContextItem
|
public class DebugContextItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The level of debug messages to print
|
/// The level of debug messages to print
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("level")]
|
[JsonProperty("level")]
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Property to tell the program not to intitialize when it boots, if desired
|
/// Property to tell the program not to intitialize when it boots, if desired
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("doNotLoadOnNextBoot")]
|
[JsonProperty("doNotLoadOnNextBoot")]
|
||||||
public bool DoNotLoadOnNextBoot { get; set; }
|
public bool DoNotLoadOnNextBoot { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,292 +1,461 @@
|
||||||
using System;
|
extern alias NewtonsoftJson;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System;
|
||||||
using System.Text;
|
using System.Security.Authentication;
|
||||||
using System.Threading.Tasks;
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Serilog.Configuration;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Serilog.Configuration;
|
|
||||||
using WebSocketSharp.Server;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using WebSocketSharp;
|
|
||||||
using System.Security.Authentication;
|
|
||||||
using WebSocketSharp.Net;
|
|
||||||
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
|
||||||
using System.IO;
|
|
||||||
using Org.BouncyCastle.Asn1.X509;
|
|
||||||
using Serilog.Formatting;
|
using Serilog.Formatting;
|
||||||
using Newtonsoft.Json.Linq;
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
using Serilog.Formatting.Json;
|
using Serilog.Formatting.Json;
|
||||||
|
using System.IO;
|
||||||
|
using WebSocketSharp;
|
||||||
|
using WebSocketSharp.Server;
|
||||||
|
using WebSocketSharp.Net;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
|
||||||
|
/// WebSocket clients.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This class implements the <see cref="ILogEventSink"/> 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.</remarks>
|
||||||
|
public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||||
{
|
{
|
||||||
|
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";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a DebugWebsocketSink
|
/// Gets the port number on which the HTTPS server is currently running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugWebsocketSink : ILogEventSink
|
public int Port
|
||||||
{
|
{
|
||||||
private HttpServer _httpsServer;
|
get
|
||||||
|
|
||||||
private string _path = "/debug/join/";
|
|
||||||
private const string _certificateName = "selfCres";
|
|
||||||
private const string _certificatePassword = "cres12345";
|
|
||||||
|
|
||||||
public int Port
|
|
||||||
{ get
|
|
||||||
{
|
|
||||||
|
|
||||||
if(_httpsServer == null) return 0;
|
|
||||||
return _httpsServer.Port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Url
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_httpsServer == null) return "";
|
|
||||||
return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{_httpsServer.WebSocketServices[_path].Path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the IsRunning
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
|
||||||
|
|
||||||
|
|
||||||
private readonly ITextFormatter _textFormatter;
|
|
||||||
|
|
||||||
public DebugWebsocketSink(ITextFormatter formatProvider)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
_textFormatter = formatProvider ?? new JsonFormatter();
|
if (_httpsServer == null) return 0;
|
||||||
|
return _httpsServer.Port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
|
/// <summary>
|
||||||
CreateCert(null);
|
/// Gets the WebSocket URL for the current server instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The URL is dynamically constructed based on the server's current IP address, port,
|
||||||
|
/// and WebSocket path.</remarks>
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether there are active WebSocket connections.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Key => "DebugWebsocketSink";
|
||||||
|
|
||||||
|
private readonly ITextFormatter _textFormatter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DebugWebsocketSink"/> class with the specified text formatter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="formatProvider">The text formatter used to format log messages. If null, a default JSON formatter is used.</param>
|
||||||
|
public DebugWebsocketSink(ITextFormatter formatProvider)
|
||||||
|
{
|
||||||
|
|
||||||
|
_textFormatter = formatProvider ?? new JsonFormatter();
|
||||||
|
|
||||||
|
if (!File.Exists(CertPath))
|
||||||
|
CreateCert();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||||
{
|
{
|
||||||
if (type == eProgramStatusEventType.Stopping)
|
if (type == eProgramStatusEventType.Stopping)
|
||||||
{
|
|
||||||
StopServer();
|
StopServer();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
private void CreateCert(string[] args)
|
|
||||||
{
|
{
|
||||||
try
|
// CrestronEnvironment is not available in test / dev environments — safe to skip.
|
||||||
{
|
|
||||||
//Debug.Console(0,"CreateCert Creating Utility");
|
|
||||||
CrestronConsole.PrintLine("CreateCert Creating Utility");
|
|
||||||
//var utility = new CertificateUtility();
|
|
||||||
var utility = new BouncyCertificate();
|
|
||||||
//Debug.Console(0, "CreateCert Calling CreateCert");
|
|
||||||
CrestronConsole.PrintLine("CreateCert Calling CreateCert");
|
|
||||||
//utility.CreateCert();
|
|
||||||
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);
|
|
||||||
|
|
||||||
//Debug.Console(0, "DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
|
|
||||||
CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
|
|
||||||
|
|
||||||
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), new[] { string.Format("{0}.{1}", hostName, domainName), ipAddress }, new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth });
|
|
||||||
//Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested
|
|
||||||
//Debug.Print($"CreateCert Storing Certificate To My.LocalMachine");
|
|
||||||
//utility.AddCertToStore(certificate, StoreName.My, StoreLocation.LocalMachine);
|
|
||||||
//Debug.Console(0, "CreateCert Saving Cert to \\user\\");
|
|
||||||
CrestronConsole.PrintLine("CreateCert Saving Cert to \\user\\");
|
|
||||||
utility.CertificatePassword = _certificatePassword;
|
|
||||||
utility.WriteCertificate(certificate, @"\user\", _certificateName);
|
|
||||||
//Debug.Console(0, "CreateCert Ending CreateCert");
|
|
||||||
CrestronConsole.PrintLine("CreateCert Ending CreateCert");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
|
|
||||||
CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static void CreateCert()
|
||||||
/// Emit method
|
{
|
||||||
/// </summary>
|
// NOTE: This method is called from the constructor, which is itself called during Debug's static
|
||||||
public void Emit(LogEvent logEvent)
|
// 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
|
||||||
{
|
{
|
||||||
if (_httpsServer == null || !_httpsServer.IsListening) return;
|
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);
|
||||||
|
|
||||||
var sw = new StringWriter();
|
CrestronConsole.PrintLine(string.Format("CreateCert: DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
|
||||||
_textFormatter.Format(logEvent, sw);
|
|
||||||
|
|
||||||
_httpsServer.WebSocketServices.Broadcast(sw.ToString());
|
var subjectName = string.Format("CN={0}.{1}", hostName, domainName);
|
||||||
|
var fqdn = string.Format("{0}.{1}", hostName, domainName);
|
||||||
|
|
||||||
}
|
using var rsa = RSA.Create(2048);
|
||||||
|
|
||||||
/// <summary>
|
var request = new CertificateRequest(
|
||||||
/// StartServerAndSetPort method
|
subjectName,
|
||||||
/// </summary>
|
rsa,
|
||||||
public void StartServerAndSetPort(int port)
|
HashAlgorithmName.SHA256,
|
||||||
{
|
RSASignaturePadding.Pkcs1);
|
||||||
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
|
|
||||||
|
|
||||||
|
// Subject Key Identifier
|
||||||
|
request.CertificateExtensions.Add(
|
||||||
|
new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
||||||
|
|
||||||
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
|
// Extended Key Usage: server + client auth
|
||||||
}
|
request.CertificateExtensions.Add(
|
||||||
|
new X509EnhancedKeyUsageExtension(
|
||||||
private void Start(int port, string certPath = "", string certPassword = "")
|
new OidCollection
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_httpsServer = new HttpServer(port, true);
|
|
||||||
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(certPath))
|
|
||||||
{
|
|
||||||
Debug.Console(0, "Assigning SSL Configuration");
|
|
||||||
_httpsServer.SslConfiguration = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword))
|
|
||||||
{
|
{
|
||||||
ClientCertificateRequired = false,
|
new Oid("1.3.6.1.5.5.7.3.1"), // id-kp-serverAuth
|
||||||
CheckCertificateRevocation = false,
|
new Oid("1.3.6.1.5.5.7.3.2") // id-kp-clientAuth
|
||||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
|
},
|
||||||
//this is just to test, you might want to actually validate
|
false));
|
||||||
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
|
||||||
{
|
// Subject Alternative Names: DNS + IP
|
||||||
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
return true;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a log event to all connected WebSocket clients.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="logEvent">The log event to be formatted and broadcasted. Cannot be null.</param>
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the WebSocket server on the specified port and configures it with the appropriate certificate.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param>
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
Debug.Console(0, "Adding Debug Client Service");
|
|
||||||
_httpsServer.AddWebSocketService<DebugClient>(_path);
|
|
||||||
Debug.Console(0, "Assigning Log Info");
|
|
||||||
_httpsServer.Log.Level = LogLevel.Trace;
|
|
||||||
_httpsServer.Log.Output = (d, s) =>
|
|
||||||
{
|
|
||||||
uint level;
|
|
||||||
|
|
||||||
switch(d.Level)
|
|
||||||
{
|
|
||||||
case WebSocketSharp.LogLevel.Fatal:
|
|
||||||
level = 3;
|
|
||||||
break;
|
|
||||||
case WebSocketSharp.LogLevel.Error:
|
|
||||||
level = 2;
|
|
||||||
break;
|
|
||||||
case WebSocketSharp.LogLevel.Warn:
|
|
||||||
level = 1;
|
|
||||||
break;
|
|
||||||
case WebSocketSharp.LogLevel.Info:
|
|
||||||
level = 0;
|
|
||||||
break;
|
|
||||||
case WebSocketSharp.LogLevel.Debug:
|
|
||||||
level = 4;
|
|
||||||
break;
|
|
||||||
case WebSocketSharp.LogLevel.Trace:
|
|
||||||
level = 5;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
level = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
|
|
||||||
};
|
|
||||||
Debug.Console(0, "Starting");
|
|
||||||
|
|
||||||
_httpsServer.Start();
|
|
||||||
Debug.Console(0, "Ready");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
|
|
||||||
}
|
}
|
||||||
|
Debug.LogInformation("Adding Debug Client Service");
|
||||||
|
_httpsServer.AddWebSocketService<DebugClient>(_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)
|
||||||
/// <summary>
|
|
||||||
/// StopServer method
|
|
||||||
/// </summary>
|
|
||||||
public void StopServer()
|
|
||||||
{
|
{
|
||||||
Debug.Console(0, "Stopping Websocket Server");
|
Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message);
|
||||||
_httpsServer?.Stop();
|
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||||
|
// Null out the server so callers can detect failure via IsRunning / Url null guards.
|
||||||
_httpsServer = null;
|
_httpsServer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DebugWebsocketSinkExtensions
|
private void HandleHttpGet(object sender, HttpRequestEventArgs e)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var res = e.Response;
|
||||||
/// DebugWebsocketSink method
|
var body = System.Text.Encoding.UTF8.GetBytes(
|
||||||
/// </summary>
|
"<html><body><h2>Certificate accepted.</h2><p>You can close this tab and return to the application.</p></body></html>");
|
||||||
public static LoggerConfiguration DebugWebsocketSink(
|
res.ContentType = "text/html";
|
||||||
this LoggerSinkConfiguration loggerConfiguration,
|
res.ContentLength64 = body.Length;
|
||||||
ITextFormatter formatProvider = null)
|
res.Close(body, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the WebSocket server if it is currently running.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
public void StopServer()
|
||||||
|
{
|
||||||
|
Debug.LogInformation("Stopping Websocket Server");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
|
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) ? "<none>" : 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the logger to write log events to a debug WebSocket sink.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This extension method allows you to direct log events to a WebSocket sink for debugging
|
||||||
|
/// purposes.</remarks>
|
||||||
|
public static class DebugWebsocketSinkExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configures a logger to write log events to a debug WebSocket sink.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="loggerConfiguration">The logger sink configuration to apply the WebSocket sink to.</param>
|
||||||
|
/// <param name="formatProvider">An optional text formatter to format the log events. If not provided, a default formatter will be used.</param>
|
||||||
|
/// <returns>A <see cref="LoggerConfiguration"/> object that can be used to further configure the logger.</returns>
|
||||||
|
public static LoggerConfiguration DebugWebsocketSink(
|
||||||
|
this LoggerSinkConfiguration loggerConfiguration,
|
||||||
|
ITextFormatter formatProvider = null)
|
||||||
|
{
|
||||||
|
return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
|
||||||
|
/// handling functionality.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The <see cref="DebugClient"/> class extends <see cref="WebSocketBehavior"/> 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.</remarks>
|
||||||
|
public class DebugClient : WebSocketBehavior
|
||||||
|
{
|
||||||
|
private DateTime _connectionTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the duration of time the WebSocket connection has been active.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan ConnectedDuration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Context.WebSocket.IsAlive)
|
||||||
|
{
|
||||||
|
return DateTime.Now - _connectionTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new TimeSpan(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a DebugClient
|
/// Initializes a new instance of the <see cref="DebugClient"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugClient : WebSocketBehavior
|
public DebugClient()
|
||||||
{
|
{
|
||||||
private DateTime _connectionTime;
|
Debug.LogInformation("DebugClient Created");
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan ConnectedDuration
|
/// <inheritdoc/>
|
||||||
{
|
protected override void OnOpen()
|
||||||
get
|
{
|
||||||
{
|
base.OnOpen();
|
||||||
if (Context.WebSocket.IsAlive)
|
|
||||||
{
|
|
||||||
return DateTime.Now - _connectionTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new TimeSpan(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugClient()
|
var url = Context.WebSocket.Url;
|
||||||
{
|
Debug.LogInformation("New WebSocket Connection from: {0}", url);
|
||||||
Debug.Console(0, "DebugClient Created");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnOpen()
|
_connectionTime = DateTime.Now;
|
||||||
{
|
}
|
||||||
base.OnOpen();
|
|
||||||
|
|
||||||
var url = Context.WebSocket.Url;
|
/// <inheritdoc/>
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
|
protected override void OnMessage(MessageEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMessage(e);
|
||||||
|
|
||||||
_connectionTime = DateTime.Now;
|
Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMessage(MessageEventArgs e)
|
/// <inheritdoc/>
|
||||||
{
|
protected override void OnClose(CloseEventArgs e)
|
||||||
base.OnMessage(e);
|
{
|
||||||
|
base.OnClose(e);
|
||||||
|
|
||||||
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
|
Debug.LogDebug("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnClose(CloseEventArgs e)
|
/// <inheritdoc/>
|
||||||
{
|
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
||||||
base.OnClose(e);
|
{
|
||||||
|
base.OnError(e);
|
||||||
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
Debug.LogError(e.Exception, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
||||||
|
Debug.LogVerbose("Stack Trace:\r{0}", e.Exception.StackTrace);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,17 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Not in use
|
||||||
|
/// </summary>
|
||||||
|
public static class NetworkComm
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not in use
|
/// Not in use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class NetworkComm
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Not in use
|
|
||||||
/// </summary>
|
|
||||||
static NetworkComm()
|
static NetworkComm()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
namespace PepperDash.Core.PasswordManagement;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// JSON password configuration
|
/// JSON password configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -22,5 +22,4 @@ namespace PepperDash.Core.PasswordManagement
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
namespace PepperDash.Core.PasswordManagement;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants
|
/// Constants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -53,5 +53,4 @@ namespace PepperDash.Core.PasswordManagement
|
||||||
/// Generic string value change constant
|
/// Generic string value change constant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort StringValueChange = 201;
|
public const ushort StringValueChange = 201;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
namespace PepperDash.Core.PasswordManagement;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a PasswordClient
|
/// A class to allow user interaction with the PasswordManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PasswordClient
|
public class PasswordClient
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -192,5 +192,4 @@ namespace PepperDash.Core.PasswordManagement
|
||||||
GetPasswordByIndex(args.Index);
|
GetPasswordByIndex(args.Index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,247 +1,252 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp;
|
using System.Timers;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
namespace PepperDash.Core.PasswordManagement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows passwords to be stored and managed
|
||||||
|
/// </summary>
|
||||||
|
public class PasswordManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a PasswordManager
|
/// Public dictionary of known passwords
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PasswordManager
|
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
|
||||||
|
/// <summary>
|
||||||
|
/// Private dictionary, used when passwords are updated
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer used to wait until password changes have stopped before updating the dictionary
|
||||||
|
/// </summary>
|
||||||
|
Timer PasswordTimer;
|
||||||
|
/// <summary>
|
||||||
|
/// Timer length
|
||||||
|
/// </summary>
|
||||||
|
public long PasswordTimerElapsedMs = 5000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Boolean event
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||||
|
/// <summary>
|
||||||
|
/// Ushort event
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||||
|
/// <summary>
|
||||||
|
/// String event
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||||
|
/// <summary>
|
||||||
|
/// Event to notify clients of an updated password at the specified index (uint)
|
||||||
|
/// </summary>
|
||||||
|
public static event EventHandler<StringChangeEventArgs> PasswordChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public PasswordManager()
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Public dictionary of known passwords
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
|
|
||||||
/// <summary>
|
|
||||||
/// Private dictionary, used when passwords are updated
|
|
||||||
/// </summary>
|
|
||||||
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
|
|
||||||
|
|
||||||
/// <summary>
|
}
|
||||||
/// Timer used to wait until password changes have stopped before updating the dictionary
|
|
||||||
/// </summary>
|
|
||||||
CTimer PasswordTimer;
|
|
||||||
/// <summary>
|
|
||||||
/// Timer length
|
|
||||||
/// </summary>
|
|
||||||
public long PasswordTimerElapsedMs = 5000;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Boolean event
|
/// Initialize password manager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
public void Initialize()
|
||||||
/// <summary>
|
{
|
||||||
/// Ushort event
|
if (Passwords == null)
|
||||||
/// </summary>
|
Passwords = new Dictionary<uint, string>();
|
||||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
|
||||||
/// <summary>
|
|
||||||
/// String event
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Event to notify clients of an updated password at the specified index (uint)
|
|
||||||
/// </summary>
|
|
||||||
public static event EventHandler<StringChangeEventArgs> PasswordChange;
|
|
||||||
|
|
||||||
/// <summary>
|
if (_passwords == null)
|
||||||
/// Constructor
|
_passwords = new Dictionary<uint, string>();
|
||||||
/// </summary>
|
|
||||||
public PasswordManager()
|
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates password stored in the dictonary
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// UpdatePassword method
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePassword(ushort key, string password)
|
||||||
|
{
|
||||||
|
// validate the parameters
|
||||||
|
if (key > 0 && string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
|
Debug.LogDebug("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
try
|
||||||
/// Initialize password manager
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize()
|
|
||||||
{
|
{
|
||||||
if (Passwords == null)
|
// if key exists, update the value
|
||||||
Passwords = new Dictionary<uint, string>();
|
if (_passwords.ContainsKey(key))
|
||||||
|
_passwords[key] = password;
|
||||||
|
// else add the key & value
|
||||||
|
else
|
||||||
|
_passwords.Add(key, password);
|
||||||
|
|
||||||
if (_passwords == null)
|
Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]);
|
||||||
_passwords = new Dictionary<uint, string>();
|
|
||||||
|
|
||||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
if (PasswordTimer == null)
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates password stored in the dictonary
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// UpdatePassword method
|
|
||||||
/// </summary>
|
|
||||||
public void UpdatePassword(ushort key, string password)
|
|
||||||
{
|
|
||||||
// validate the parameters
|
|
||||||
if (key > 0 && string.IsNullOrEmpty(password))
|
|
||||||
{
|
{
|
||||||
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password));
|
PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false };
|
||||||
return;
|
PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e);
|
||||||
|
PasswordTimer.Start();
|
||||||
|
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started");
|
||||||
|
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
try
|
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
|
|
||||||
Debug.Console(1, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CTimer callback function
|
|
||||||
/// </summary>
|
|
||||||
private void PasswordTimerElapsed()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
PasswordTimer.Stop();
|
PasswordTimer.Stop();
|
||||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped"));
|
PasswordTimer.Interval = PasswordTimerElapsedMs;
|
||||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
PasswordTimer.Start();
|
||||||
foreach (var pw in _passwords)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timer callback function
|
||||||
|
/// </summary>
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// if key exists, continue
|
||||||
|
if (Passwords.ContainsKey(pw.Key))
|
||||||
{
|
{
|
||||||
// if key exists, continue
|
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value);
|
||||||
if (Passwords.ContainsKey(pw.Key))
|
if (Passwords[pw.Key] != _passwords[pw.Key])
|
||||||
{
|
{
|
||||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value));
|
Passwords[pw.Key] = _passwords[pw.Key];
|
||||||
if (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);
|
||||||
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);
|
// else add the key & value
|
||||||
}
|
else
|
||||||
catch (Exception e)
|
{
|
||||||
{
|
Passwords.Add(pw.Key, pw.Value);
|
||||||
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e);
|
}
|
||||||
Debug.Console(1, msg);
|
|
||||||
}
|
}
|
||||||
|
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// Method to change the default timer value, (default 5000ms/5s)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// PasswordTimerMs method
|
|
||||||
/// </summary>
|
|
||||||
public void PasswordTimerMs(ushort time)
|
|
||||||
{
|
{
|
||||||
PasswordTimerElapsedMs = Convert.ToInt64(time);
|
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message);
|
||||||
|
Debug.LogError(ex, msg);
|
||||||
|
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method for debugging to see what passwords are in the lists
|
/// Method to change the default timer value, (default 5000ms/5s)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ListPasswords()
|
/// <param name="time"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// PasswordTimerMs method
|
||||||
|
/// </summary>
|
||||||
|
public void PasswordTimerMs(ushort time)
|
||||||
|
{
|
||||||
|
PasswordTimerElapsedMs = Convert.ToInt64(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper method for debugging to see what passwords are in the lists
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected boolean change event handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = BoolChange;
|
||||||
|
if (handler != null)
|
||||||
{
|
{
|
||||||
Debug.Console(0, "PasswordManager.ListPasswords:\r");
|
var args = new BoolChangeEventArgs(state, type);
|
||||||
foreach (var pw in Passwords)
|
args.Index = index;
|
||||||
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
BoolChange(this, args);
|
||||||
Debug.Console(0, "\n");
|
|
||||||
foreach (var pw in _passwords)
|
|
||||||
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Protected boolean change event handler
|
/// Protected ushort change event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = UshrtChange;
|
||||||
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var handler = BoolChange;
|
var args = new UshrtChangeEventArgs(value, type);
|
||||||
if (handler != null)
|
args.Index = index;
|
||||||
{
|
UshrtChange(this, args);
|
||||||
var args = new BoolChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
BoolChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Protected ushort change event handler
|
/// Protected string change event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
protected void OnStringChange(string value, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = StringChange;
|
||||||
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var handler = UshrtChange;
|
var args = new StringChangeEventArgs(value, type);
|
||||||
if (handler != null)
|
args.Index = index;
|
||||||
{
|
StringChange(this, args);
|
||||||
var args = new UshrtChangeEventArgs(value, type);
|
|
||||||
args.Index = index;
|
|
||||||
UshrtChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Protected string change event handler
|
/// Protected password change event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
protected void OnStringChange(string value, ushort index, ushort type)
|
protected void OnPasswordChange(string value, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = PasswordChange;
|
||||||
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var handler = StringChange;
|
var args = new StringChangeEventArgs(value, type);
|
||||||
if (handler != null)
|
args.Index = index;
|
||||||
{
|
PasswordChange(this, args);
|
||||||
var args = new StringChangeEventArgs(value, type);
|
|
||||||
args.Index = index;
|
|
||||||
StringChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected password change event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>PepperDash.Core</RootNamespace>
|
<RootNamespace>PepperDash.Core</RootNamespace>
|
||||||
<AssemblyName>PepperDashCore</AssemblyName>
|
<AssemblyName>PepperDashCore</AssemblyName>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net8</TargetFramework>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
|
|
@ -35,25 +35,23 @@
|
||||||
<EmbeddedResource Remove="Properties\**" />
|
<EmbeddedResource Remove="Properties\**" />
|
||||||
<None Remove="lib\**" />
|
<None Remove="lib\**" />
|
||||||
<None Remove="Properties\**" />
|
<None Remove="Properties\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" />
|
||||||
|
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.128" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4">
|
||||||
|
<Aliases>global,NewtonsoftJson</Aliases>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||||
|
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
||||||
|
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
|
|
||||||
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" />
|
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
|
||||||
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
|
||||||
<PackageReference Include="SSH.NET" Version="2024.2.0" />
|
|
||||||
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6'">
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
<Compile Remove="Comm\._GenericSshClient.cs" />
|
||||||
|
|
|
||||||
|
|
@ -4,68 +4,68 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
namespace PepperDash.Core.SystemInfo;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants
|
/// Constants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemInfoConstants
|
public class SystemInfoConstants
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort BoolValueChange = 1;
|
public const ushort BoolValueChange = 1;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort CompleteBoolChange = 2;
|
public const ushort CompleteBoolChange = 2;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort BusyBoolChange = 3;
|
public const ushort BusyBoolChange = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort UshortValueChange = 101;
|
public const ushort UshortValueChange = 101;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort StringValueChange = 201;
|
public const ushort StringValueChange = 201;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ConsoleResponseChange = 202;
|
public const ushort ConsoleResponseChange = 202;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProcessorUptimeChange = 203;
|
public const ushort ProcessorUptimeChange = 203;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProgramUptimeChange = 204;
|
public const ushort ProgramUptimeChange = 204;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ObjectChange = 301;
|
public const ushort ObjectChange = 301;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProcessorConfigChange = 302;
|
public const ushort ProcessorConfigChange = 302;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort EthernetConfigChange = 303;
|
public const ushort EthernetConfigChange = 303;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ControlSubnetConfigChange = 304;
|
public const ushort ControlSubnetConfigChange = 304;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort ProgramConfigChange = 305;
|
public const ushort ProgramConfigChange = 305;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -73,18 +73,18 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProcessorChangeEventArgs : EventArgs
|
public class ProcessorChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ProcessorInfo Processor { get; set; }
|
public ProcessorInfo Processor { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Type { get; set; }
|
public ushort Type { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Index { get; set; }
|
public ushort Index { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -119,18 +119,18 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EthernetChangeEventArgs : EventArgs
|
public class EthernetChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public EthernetInfo Adapter { get; set; }
|
public EthernetInfo Adapter { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Type { get; set; }
|
public ushort Type { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Index { get; set; }
|
public ushort Index { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -143,7 +143,7 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor overload
|
/// Constructor overload
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ethernet"></param>
|
/// <param name="ethernet"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type)
|
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type)
|
||||||
{
|
{
|
||||||
|
|
@ -154,9 +154,9 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor overload
|
/// Constructor overload
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ethernet"></param>
|
/// <param name="ethernet"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index)
|
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index)
|
||||||
{
|
{
|
||||||
Adapter = ethernet;
|
Adapter = ethernet;
|
||||||
|
|
@ -170,18 +170,18 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControlSubnetChangeEventArgs : EventArgs
|
public class ControlSubnetChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ControlSubnetInfo Adapter { get; set; }
|
public ControlSubnetInfo Adapter { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Type { get; set; }
|
public ushort Type { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Index { get; set; }
|
public ushort Index { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -216,18 +216,18 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProgramChangeEventArgs : EventArgs
|
public class ProgramChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ProgramInfo Program { get; set; }
|
public ProgramInfo Program { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Type { get; set; }
|
public ushort Type { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Index { get; set; }
|
public ushort Index { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -240,7 +240,7 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor overload
|
/// Constructor overload
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="program"></param>
|
/// <param name="program"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
public ProgramChangeEventArgs(ProgramInfo program, ushort type)
|
public ProgramChangeEventArgs(ProgramInfo program, ushort type)
|
||||||
{
|
{
|
||||||
|
|
@ -251,14 +251,13 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor overload
|
/// Constructor overload
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="program"></param>
|
/// <param name="program"></param>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <param name="index"></param>
|
/// <param name="index"></param>
|
||||||
public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index)
|
public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index)
|
||||||
{
|
{
|
||||||
Program = program;
|
Program = program;
|
||||||
Type = type;
|
Type = type;
|
||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,52 +4,52 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
namespace PepperDash.Core.SystemInfo;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processor info class
|
/// Processor info class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProcessorInfo
|
public class ProcessorInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SerialNumber { get; set; }
|
public string SerialNumber { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Firmware { get; set; }
|
public string Firmware { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FirmwareDate { get; set; }
|
public string FirmwareDate { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OsVersion { get; set; }
|
public string OsVersion { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RuntimeEnvironment { get; set; }
|
public string RuntimeEnvironment { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DevicePlatform { get; set; }
|
public string DevicePlatform { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModuleDirectory { get; set; }
|
public string ModuleDirectory { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LocalTimeZone { get; set; }
|
public string LocalTimeZone { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ProgramIdTag { get; set; }
|
public string ProgramIdTag { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -66,45 +66,45 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EthernetInfo
|
public class EthernetInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort DhcpIsOn { get; set; }
|
public ushort DhcpIsOn { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hostname { get; set; }
|
public string Hostname { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string MacAddress { get; set; }
|
public string MacAddress { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Subnet { get; set; }
|
public string Subnet { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Gateway { get; set; }
|
public string Gateway { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Dns1 { get; set; }
|
public string Dns1 { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Dns2 { get; set; }
|
public string Dns2 { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Dns3 { get; set; }
|
public string Dns3 { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Domain { get; set; }
|
public string Domain { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -121,29 +121,29 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControlSubnetInfo
|
public class ControlSubnetInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Enabled { get; set; }
|
public ushort Enabled { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort IsInAutomaticMode { get; set; }
|
public ushort IsInAutomaticMode { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string MacAddress { get; set; }
|
public string MacAddress { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Subnet { get; set; }
|
public string Subnet { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RouterPrefix { get; set; }
|
public string RouterPrefix { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -160,37 +160,37 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProgramInfo
|
public class ProgramInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Header { get; set; }
|
public string Header { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string System { get; set; }
|
public string System { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ProgramIdTag { get; set; }
|
public string ProgramIdTag { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CompileTime { get; set; }
|
public string CompileTime { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Database { get; set; }
|
public string Database { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Environment { get; set; }
|
public string Environment { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Programmer { get; set; }
|
public string Programmer { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -200,5 +200,4 @@ namespace PepperDash.Core.SystemInfo
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -4,37 +4,37 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
namespace PepperDash.Core.SystemInfo;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// System Info class
|
/// System Info class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemInfoToSimpl
|
public class SystemInfoToSimpl
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of bool change
|
/// Notifies of bool change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of string change
|
/// Notifies of string change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of processor change
|
/// Notifies of processor change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ProcessorChangeEventArgs> ProcessorChange;
|
public event EventHandler<ProcessorChangeEventArgs> ProcessorChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of ethernet change
|
/// Notifies of ethernet change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<EthernetChangeEventArgs> EthernetChange;
|
public event EventHandler<EthernetChangeEventArgs> EthernetChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of control subnet change
|
/// Notifies of control subnet change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ControlSubnetChangeEventArgs> ControlSubnetChange;
|
public event EventHandler<ControlSubnetChangeEventArgs> ControlSubnetChange;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies of program change
|
/// Notifies of program change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ProgramChangeEventArgs> ProgramChange;
|
public event EventHandler<ProgramChangeEventArgs> ProgramChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -336,10 +336,10 @@ namespace PepperDash.Core.SystemInfo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// private method to parse console messages
|
/// private method to parse console messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
/// <param name="line"></param>
|
/// <param name="line"></param>
|
||||||
/// <param name="dataStart"></param>
|
/// <param name="dataStart"></param>
|
||||||
/// <param name="dataEnd"></param>
|
/// <param name="dataEnd"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd)
|
private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd)
|
||||||
{
|
{
|
||||||
|
|
@ -467,5 +467,4 @@ namespace PepperDash.Core.SystemInfo
|
||||||
ProgramChange(this, args);
|
ProgramChange(this, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -20,352 +20,336 @@ using Org.BouncyCastle.Crypto.Operators;
|
||||||
using BigInteger = Org.BouncyCastle.Math.BigInteger;
|
using BigInteger = Org.BouncyCastle.Math.BigInteger;
|
||||||
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
||||||
|
|
||||||
namespace PepperDash.Core
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
|
||||||
|
/// </summary>
|
||||||
|
internal class BouncyCertificate
|
||||||
{
|
{
|
||||||
/// <summary>
|
public string CertificatePassword { get; set; } = "password";
|
||||||
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
|
public X509Certificate2 LoadCertificate(string issuerFileName, string password)
|
||||||
/// </summary>
|
|
||||||
internal class BouncyCertificate
|
|
||||||
{
|
{
|
||||||
public string CertificatePassword { get; set; } = "password";
|
// We need to pass 'Exportable', otherwise we can't get the private key.
|
||||||
public X509Certificate2 LoadCertificate(string issuerFileName, string password)
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="random"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private BigInteger GenerateSerialNumber(SecureRandom random)
|
||||||
|
{
|
||||||
|
var serialNumber =
|
||||||
|
BigIntegers.CreateRandomInRange(
|
||||||
|
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
|
||||||
|
return serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a key pair.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="random">The random number generator.</param>
|
||||||
|
/// <param name="strength">The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateGenerator"></param>
|
||||||
|
/// <param name="issuerDN"></param>
|
||||||
|
/// <param name="issuerKeyPair"></param>
|
||||||
|
/// <param name="issuerSerialNumber"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the "Subject Alternative Names" extension. Note that you have to repeat
|
||||||
|
/// the value from the "Subject Name" property.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateGenerator"></param>
|
||||||
|
/// <param name="subjectAlternativeNames"></param>
|
||||||
|
private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator,
|
||||||
|
IEnumerable<string> subjectAlternativeNames)
|
||||||
|
{
|
||||||
|
var subjectAlternativeNamesExtension =
|
||||||
|
new DerSequence(
|
||||||
|
subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name))
|
||||||
|
.ToArray<Asn1Encodable>());
|
||||||
|
certificateGenerator.AddExtension(
|
||||||
|
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the "Extended Key Usage" extension, specifying (for example) "server authentication".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateGenerator"></param>
|
||||||
|
/// <param name="usages"></param>
|
||||||
|
private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages)
|
||||||
|
{
|
||||||
|
certificateGenerator.AddExtension(
|
||||||
|
X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the "Basic Constraints" extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateGenerator"></param>
|
||||||
|
/// <param name="isCertificateAuthority"></param>
|
||||||
|
private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator,
|
||||||
|
bool isCertificateAuthority)
|
||||||
|
{
|
||||||
|
certificateGenerator.AddExtension(
|
||||||
|
X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the Subject Key Identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificateGenerator"></param>
|
||||||
|
/// <param name="subjectKeyPair"></param>
|
||||||
|
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
|
||||||
{
|
{
|
||||||
// We need to pass 'Exportable', otherwise we can't get the private key.
|
var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword);
|
||||||
var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable);
|
File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx);
|
||||||
return issuerCertificate;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// IssueCertificate method
|
|
||||||
/// </summary>
|
|
||||||
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
|
||||||
{
|
{
|
||||||
// It's self-signed, so these are the same.
|
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message));
|
||||||
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)
|
||||||
/// <summary>
|
using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false))
|
||||||
/// CreateCertificateAuthorityCertificate method
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CreateSelfSignedCertificate method
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="random"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private BigInteger GenerateSerialNumber(SecureRandom random)
|
|
||||||
{
|
|
||||||
var serialNumber =
|
|
||||||
BigIntegers.CreateRandomInRange(
|
|
||||||
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
|
|
||||||
return serialNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a key pair.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="random">The random number generator.</param>
|
|
||||||
/// <param name="strength">The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificateGenerator"></param>
|
|
||||||
/// <param name="issuerDN"></param>
|
|
||||||
/// <param name="issuerKeyPair"></param>
|
|
||||||
/// <param name="issuerSerialNumber"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add the "Subject Alternative Names" extension. Note that you have to repeat
|
|
||||||
/// the value from the "Subject Name" property.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificateGenerator"></param>
|
|
||||||
/// <param name="subjectAlternativeNames"></param>
|
|
||||||
private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator,
|
|
||||||
IEnumerable<string> subjectAlternativeNames)
|
|
||||||
{
|
|
||||||
var subjectAlternativeNamesExtension =
|
|
||||||
new DerSequence(
|
|
||||||
subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name))
|
|
||||||
.ToArray<Asn1Encodable>());
|
|
||||||
certificateGenerator.AddExtension(
|
|
||||||
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add the "Extended Key Usage" extension, specifying (for example) "server authentication".
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificateGenerator"></param>
|
|
||||||
/// <param name="usages"></param>
|
|
||||||
private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages)
|
|
||||||
{
|
|
||||||
certificateGenerator.AddExtension(
|
|
||||||
X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add the "Basic Constraints" extension.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificateGenerator"></param>
|
|
||||||
/// <param name="isCertificateAuthority"></param>
|
|
||||||
private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator,
|
|
||||||
bool isCertificateAuthority)
|
|
||||||
{
|
|
||||||
certificateGenerator.AddExtension(
|
|
||||||
X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add the Subject Key Identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificateGenerator"></param>
|
|
||||||
/// <param name="subjectKeyPair"></param>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// WriteCertificate method
|
|
||||||
/// </summary>
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword);
|
var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
|
||||||
File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx);
|
writer.Write(contents);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message));
|
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// AddCertToStore method
|
|
||||||
/// </summary>
|
|
||||||
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
|
|
||||||
{
|
|
||||||
bool bRet = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl);
|
|
||||||
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
|
|
||||||
store.Add(cert);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
|
||||||
|
{
|
||||||
|
bool bRet = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl);
|
||||||
|
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
|
||||||
|
store.Add(cert);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
namespace PepperDash.Core.Web.RequestHandlers;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a DefaultRequestHandler
|
/// Web API default request handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultRequestHandler : WebApiBaseRequestHandler
|
public class DefaultRequestHandler : WebApiBaseRequestHandler
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -13,5 +13,4 @@ namespace PepperDash.Core.Web.RequestHandlers
|
||||||
public DefaultRequestHandler()
|
public DefaultRequestHandler()
|
||||||
: base(true)
|
: base(true)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -3,164 +3,160 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
namespace PepperDash.Core.Web.RequestHandlers;
|
||||||
|
|
||||||
|
public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
||||||
{
|
{
|
||||||
public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
|
||||||
|
protected readonly bool EnableCors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
protected WebApiBaseRequestAsyncHandler(bool enableCors)
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
|
EnableCors = enableCors;
|
||||||
protected readonly bool EnableCors;
|
|
||||||
|
|
||||||
/// <summary>
|
_handlers = new Dictionary<string, Func<HttpCwsContext, Task>>
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestAsyncHandler(bool enableCors)
|
|
||||||
{
|
{
|
||||||
EnableCors = enableCors;
|
{"CONNECT", HandleConnect},
|
||||||
|
{"DELETE", HandleDelete},
|
||||||
|
{"GET", HandleGet},
|
||||||
|
{"HEAD", HandleHead},
|
||||||
|
{"OPTIONS", HandleOptions},
|
||||||
|
{"PATCH", HandlePatch},
|
||||||
|
{"POST", HandlePost},
|
||||||
|
{"PUT", HandlePut},
|
||||||
|
{"TRACE", HandleTrace}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_handlers = new Dictionary<string, Func<HttpCwsContext, Task>>
|
/// <summary>
|
||||||
{
|
/// Constructor
|
||||||
{"CONNECT", HandleConnect},
|
/// </summary>
|
||||||
{"DELETE", HandleDelete},
|
protected WebApiBaseRequestAsyncHandler()
|
||||||
{"GET", HandleGet},
|
: this(false)
|
||||||
{"HEAD", HandleHead},
|
{
|
||||||
{"OPTIONS", HandleOptions},
|
}
|
||||||
{"PATCH", HandlePatch},
|
|
||||||
{"POST", HandlePost},
|
/// <summary>
|
||||||
{"PUT", HandlePut},
|
/// Handles CONNECT method requests
|
||||||
{"TRACE", HandleTrace}
|
/// </summary>
|
||||||
};
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleConnect(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles DELETE method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleDelete(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles GET method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleGet(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles HEAD method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleHead(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles OPTIONS method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleOptions(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles PATCH method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandlePatch(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles POST method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandlePost(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles PUT method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandlePut(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles TRACE method requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
protected virtual async Task HandleTrace(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 501;
|
||||||
|
context.Response.StatusDescription = "Not Implemented";
|
||||||
|
context.Response.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
public void ProcessRequest(HttpCwsContext context)
|
||||||
|
{
|
||||||
|
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> handler))
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (EnableCors)
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestAsyncHandler()
|
|
||||||
: this(false)
|
|
||||||
{
|
{
|
||||||
|
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||||
|
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var handlerTask = handler(context);
|
||||||
/// Handles CONNECT method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleConnect(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
handlerTask.GetAwaiter().GetResult();
|
||||||
/// Handles DELETE method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleDelete(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles GET method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleGet(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles HEAD method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleHead(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles OPTIONS method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleOptions(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles PATCH method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandlePatch(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles POST method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandlePost(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles PUT method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandlePut(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles TRACE method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual async Task HandleTrace(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 501;
|
|
||||||
context.Response.StatusDescription = "Not Implemented";
|
|
||||||
context.Response.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process request
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// ProcessRequest method
|
|
||||||
/// </summary>
|
|
||||||
public void ProcessRequest(HttpCwsContext context)
|
|
||||||
{
|
|
||||||
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
namespace PepperDash.Core.Web.RequestHandlers;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CWS Base Handler, implements IHttpCwsHandler
|
/// CWS Base Handler, implements IHttpCwsHandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -164,5 +164,4 @@ namespace PepperDash.Core.Web.RequestHandlers
|
||||||
|
|
||||||
handler(context);
|
handler(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,287 +1,269 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Core.Web.RequestHandlers;
|
using PepperDash.Core.Web.RequestHandlers;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web
|
namespace PepperDash.Core.Web;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Web API server
|
||||||
|
/// </summary>
|
||||||
|
public class WebApiServer : IKeyName
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Web API server
|
/// Gets or sets the Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebApiServer : IKeyName
|
public string Key { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the BasePath
|
||||||
|
/// </summary>
|
||||||
|
public string BasePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the IsRegistered
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRegistered { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for S+. Make sure to set necessary properties using init method
|
||||||
|
/// </summary>
|
||||||
|
public WebApiServer()
|
||||||
|
: this(SplusKey, DefaultName, null)
|
||||||
{
|
{
|
||||||
private const string SplusKey = "Uninitialized Web API Server";
|
}
|
||||||
private const string DefaultName = "Web API Server";
|
|
||||||
private const string DefaultBasePath = "/api";
|
|
||||||
|
|
||||||
private const uint DebugTrace = 0;
|
/// <summary>
|
||||||
private const uint DebugInfo = 1;
|
/// Constructor
|
||||||
private const uint DebugVerbose = 2;
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="basePath"></param>
|
||||||
|
public WebApiServer(string key, string basePath)
|
||||||
|
: this(key, DefaultName, basePath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private readonly CCriticalSection _serverLock = new CCriticalSection();
|
/// <summary>
|
||||||
private HttpCwsServer _server;
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="basePath"></param>
|
||||||
|
public WebApiServer(string key, string name, string basePath)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
|
||||||
|
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
||||||
|
|
||||||
/// <summary>
|
this.LogInformation("Creating Web API Server with Key: {Key}, Name: {Name}, BasePath: {BasePath}", Key, Name, BasePath);
|
||||||
/// Gets or sets the Key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
if (_server == null) _server = new HttpCwsServer(BasePath);
|
||||||
/// Gets or sets the Name
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
_server.AuthenticateAllRoutes = false;
|
||||||
/// Gets or sets the BasePath
|
|
||||||
/// </summary>
|
|
||||||
public string BasePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
_server.setProcessName(Key);
|
||||||
/// Gets or sets the IsRegistered
|
_server.HttpRequestHandler = new DefaultRequestHandler();
|
||||||
/// </summary>
|
_server.ReceivedRequestEvent += ReceivedRequestEventHandler;
|
||||||
public bool IsRegistered { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||||
/// Http request handler
|
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
||||||
/// </summary>
|
}
|
||||||
//public IHttpCwsHandler HttpRequestHandler
|
|
||||||
//{
|
|
||||||
// get { return _server.HttpRequestHandler; }
|
|
||||||
// set
|
|
||||||
// {
|
|
||||||
// if (_server == null) return;
|
|
||||||
// _server.HttpRequestHandler = value;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received request event handler
|
/// Program status event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
|
/// <param name="programEventType"></param>
|
||||||
//{
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
|
{
|
||||||
// remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
|
if (programEventType != eProgramStatusEventType.Stopping) return;
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
this.LogInformation("Program stopping. stopping server");
|
||||||
/// Constructor for S+. Make sure to set necessary properties using init method
|
|
||||||
/// </summary>
|
Stop();
|
||||||
public WebApiServer()
|
}
|
||||||
: this(SplusKey, DefaultName, null)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ethernet event handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ethernetEventArgs"></param>
|
||||||
|
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
||||||
|
{
|
||||||
|
if (ethernetEventArgs.EthernetEventType != eEthernetEventType.LinkUp)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (IsRegistered)
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="basePath"></param>
|
|
||||||
public WebApiServer(string key, string basePath)
|
|
||||||
: this(key, DefaultName, basePath)
|
|
||||||
{
|
{
|
||||||
|
this.LogInformation("Ethernet link up. Server is already registered.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.LogInformation("Ethernet link up. Starting server");
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
Start();
|
||||||
/// <param name="key"></param>
|
}
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <param name="basePath"></param>
|
// /// <summary>
|
||||||
public WebApiServer(string key, string name, string basePath)
|
// /// Initialize method
|
||||||
|
// /// </summary>
|
||||||
|
// public void Initialize(string key, string basePath)
|
||||||
|
// {
|
||||||
|
// Key = key;
|
||||||
|
// BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a route to CWS
|
||||||
|
/// </summary>
|
||||||
|
public void AddRoute(HttpCwsRoute route)
|
||||||
|
{
|
||||||
|
if (route == null)
|
||||||
{
|
{
|
||||||
Key = key;
|
this.LogWarning("Failed to add route, route parameter is null");
|
||||||
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
|
return;
|
||||||
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
|
||||||
|
|
||||||
if (_server == null) _server = new HttpCwsServer(BasePath);
|
|
||||||
|
|
||||||
_server.setProcessName(Key);
|
|
||||||
_server.HttpRequestHandler = new DefaultRequestHandler();
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
|
||||||
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_server.Routes.Add(route);
|
||||||
/// Program status event handler
|
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="programEventType"></param>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
/// <summary>
|
||||||
|
/// Removes a route from CWS
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="route"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// RemoveRoute method
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveRoute(HttpCwsRoute route)
|
||||||
|
{
|
||||||
|
if (route == null)
|
||||||
{
|
{
|
||||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
this.LogWarning("Failed to remove route, route parameter is null");
|
||||||
|
return;
|
||||||
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
|
||||||
|
|
||||||
Stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_server.Routes.Remove(route);
|
||||||
/// Ethernet event handler
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernetEventArgs"></param>
|
/// <summary>
|
||||||
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
/// Sets the fallback request handler that is invoked when no registered route
|
||||||
|
/// matches an incoming request. Must be called before <see cref="Start"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The handler to use as the server-level fallback.</param>
|
||||||
|
public void SetFallbackHandler(IHttpCwsHandler handler)
|
||||||
|
{
|
||||||
|
if (handler == null)
|
||||||
{
|
{
|
||||||
// Re-enable the server if the link comes back up and the status should be connected
|
this.LogWarning("SetFallbackHandler: handler parameter is null, ignoring");
|
||||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
|
return;
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
|
|
||||||
|
|
||||||
Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_server.HttpRequestHandler = handler;
|
||||||
/// Initialize method
|
}
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key, string basePath)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a route to CWS
|
/// GetRouteCollection method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddRoute(HttpCwsRoute route)
|
public HttpCwsRouteCollection GetRouteCollection()
|
||||||
{
|
{
|
||||||
if (route == null)
|
return _server.Routes;
|
||||||
{
|
}
|
||||||
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_server.Routes.Add(route);
|
/// <summary>
|
||||||
|
/// Starts CWS instance
|
||||||
}
|
/// </summary>
|
||||||
|
public void Start()
|
||||||
/// <summary>
|
{
|
||||||
/// Removes a route from CWS
|
lock (_serverLock)
|
||||||
/// </summary>
|
|
||||||
/// <param name="route"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// RemoveRoute method
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveRoute(HttpCwsRoute route)
|
|
||||||
{
|
|
||||||
if (route == null)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_server.Routes.Remove(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// GetRouteCollection method
|
|
||||||
/// </summary>
|
|
||||||
public HttpCwsRouteCollection GetRouteCollection()
|
|
||||||
{
|
|
||||||
return _server.Routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts CWS instance
|
|
||||||
/// </summary>
|
|
||||||
public void Start()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
this.LogDebug("Server is null, unable to start");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsRegistered)
|
if (IsRegistered)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server has already been started");
|
this.LogDebug("Server has already been started");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Register();
|
IsRegistered = _server.Register();
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
this.LogDebug("Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
this.LogException(ex, "Start Exception Message: {0}", ex.Message);
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
|
this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace);
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
}
|
||||||
finally
|
} // end lock
|
||||||
{
|
}
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop method
|
/// Stop method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
{
|
||||||
|
lock (_serverLock)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
|
this.LogDebug("Server is null or has already been stopped");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Unregister() == false;
|
var unregistered = _server.Unregister();
|
||||||
|
IsRegistered = !unregistered;
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
this.LogDebug("Stopping server, unregistration {0}", unregistered ? "was successful" : "failed");
|
||||||
|
|
||||||
_server.Dispose();
|
|
||||||
_server = null;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
|
this.LogException(ex, "Server Stop Exception Message: {0}", ex.Message);
|
||||||
Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
}
|
||||||
finally
|
} // end lock
|
||||||
{
|
}
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received request handler
|
/// Received request handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is here for development and testing
|
/// This is here for development and testing
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="args"></param>
|
/// <param name="args"></param>
|
||||||
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
|
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
var req = args.Context?.Request;
|
||||||
{
|
this.LogVerbose("ReceivedRequestEventHandler: {Method} {Path}", req?.HttpMethod, req?.Path);
|
||||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
}
|
||||||
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
|
||||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.WebApi.Presets
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a Preset
|
|
||||||
/// </summary>
|
|
||||||
public class Preset
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ID of preset
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the UserId
|
|
||||||
/// </summary>
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the RoomTypeId
|
|
||||||
/// </summary>
|
|
||||||
public int RoomTypeId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the PresetName
|
|
||||||
/// </summary>
|
|
||||||
public string PresetName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the PresetNumber
|
|
||||||
/// </summary>
|
|
||||||
public int PresetNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Data
|
|
||||||
/// </summary>
|
|
||||||
public string Data { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public Preset()
|
|
||||||
{
|
|
||||||
PresetName = "";
|
|
||||||
PresetNumber = 1;
|
|
||||||
Data = "{}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a PresetReceivedEventArgs
|
|
||||||
/// </summary>
|
|
||||||
public class PresetReceivedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// True when the preset is found
|
|
||||||
/// </summary>
|
|
||||||
public bool LookupSuccess { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ULookupSuccess
|
|
||||||
/// </summary>
|
|
||||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Preset
|
|
||||||
/// </summary>
|
|
||||||
public Preset Preset { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For Simpl+
|
|
||||||
/// </summary>
|
|
||||||
public PresetReceivedEventArgs() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="preset"></param>
|
|
||||||
/// <param name="success"></param>
|
|
||||||
public PresetReceivedEventArgs(Preset preset, bool success)
|
|
||||||
{
|
|
||||||
LookupSuccess = success;
|
|
||||||
Preset = preset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.WebApi.Presets
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ExternalId
|
|
||||||
/// </summary>
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the FirstName
|
|
||||||
/// </summary>
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the LastName
|
|
||||||
/// </summary>
|
|
||||||
public string LastName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class UserReceivedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// True when user is found
|
|
||||||
/// </summary>
|
|
||||||
public bool LookupSuccess { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ULookupSuccess
|
|
||||||
/// </summary>
|
|
||||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the User
|
|
||||||
/// </summary>
|
|
||||||
public User User { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For Simpl+
|
|
||||||
/// </summary>
|
|
||||||
public UserReceivedEventArgs() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user"></param>
|
|
||||||
/// <param name="success"></param>
|
|
||||||
public UserReceivedEventArgs(User user, bool success)
|
|
||||||
{
|
|
||||||
LookupSuccess = success;
|
|
||||||
User = user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a UserAndRoomMessage
|
|
||||||
/// </summary>
|
|
||||||
public class UserAndRoomMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the RoomTypeId
|
|
||||||
/// </summary>
|
|
||||||
public int RoomTypeId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the PresetNumber
|
|
||||||
/// </summary>
|
|
||||||
public int PresetNumber { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Passcode client for the WebApi
|
|
||||||
/// </summary>
|
|
||||||
public class WebApiPasscodeClient : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies when user received
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<UserReceivedEventArgs> UserReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies when Preset received
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<PresetReceivedEventArgs> PresetReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
|
||||||
|
|
||||||
//string JsonMasterKey;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An embedded JsonToSimpl master object.
|
|
||||||
/// </summary>
|
|
||||||
JsonToSimplGenericMaster J2SMaster;
|
|
||||||
|
|
||||||
string UrlBase;
|
|
||||||
|
|
||||||
string DefaultPresetJsonFilePath;
|
|
||||||
|
|
||||||
User CurrentUser;
|
|
||||||
|
|
||||||
Preset CurrentPreset;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SIMPL+ can only execute the default constructor. If you have variables that require initialization, please
|
|
||||||
/// use an Initialize method
|
|
||||||
/// </summary>
|
|
||||||
public WebApiPasscodeClient()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the instance
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="jsonMasterKey"></param>
|
|
||||||
/// <param name="urlBase"></param>
|
|
||||||
/// <param name="defaultPresetJsonFilePath"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the user for a passcode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="passcode"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// GetUserForPasscode method
|
|
||||||
/// </summary>
|
|
||||||
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<User>(resp.ContentString);
|
|
||||||
CurrentUser = user;
|
|
||||||
if (handler != null)
|
|
||||||
UserReceived(this, new UserReceivedEventArgs(user, true));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (handler != null)
|
|
||||||
UserReceived(this, new UserReceivedEventArgs(null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="roomTypeId"></param>
|
|
||||||
/// <param name="presetNumber"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// GetPresetForThisUser method
|
|
||||||
/// </summary>
|
|
||||||
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<Preset>(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)
|
|
||||||
PresetReceived(this, new PresetReceivedEventArgs(preset, true));
|
|
||||||
}
|
|
||||||
else // no existing preset
|
|
||||||
{
|
|
||||||
CurrentPreset = new Preset();
|
|
||||||
LoadDefaultPresetData();
|
|
||||||
if (handler != null)
|
|
||||||
PresetReceived(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="roomTypeId"></param>
|
|
||||||
/// <param name="presetNumber"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SavePresetForThisUser method
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// After save operation on JSON master happens, send it to server
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json"></param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
using PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Serialization
|
namespace PepperDash.Core.Intersystem.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface to determine XSig serialization for an object.
|
||||||
|
/// </summary>
|
||||||
|
public interface IXSigSerialization
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface to determine XSig serialization for an object.
|
/// Serialize the sig data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IXSigSerialization
|
/// <returns></returns>
|
||||||
{
|
IEnumerable<XSigToken> Serialize();
|
||||||
/// <summary>
|
|
||||||
/// Serialize the sig data
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
IEnumerable<XSigToken> Serialize();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize the sig data
|
/// Deserialize the sig data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="tokens"></param>
|
/// <param name="tokens"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
|
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Serialization
|
namespace PepperDash.Core.Intersystem.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class to handle this specific exception type
|
||||||
|
/// </summary>
|
||||||
|
public class XSigSerializationException : Exception
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class to handle this specific exception type
|
/// default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class XSigSerializationException : Exception
|
public XSigSerializationException() { }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// default constructor
|
|
||||||
/// </summary>
|
|
||||||
public XSigSerializationException() { }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// constructor with message
|
/// constructor with message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
public XSigSerializationException(string message) : base(message) { }
|
public XSigSerializationException(string message) : base(message) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// constructor with message and innner exception
|
/// constructor with message and innner exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <param name="inner"></param>
|
/// <param name="inner"></param>
|
||||||
public XSigSerializationException(string message, Exception inner) : base(message, inner) { }
|
public XSigSerializationException(string message, Exception inner) : base(message, inner) { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,98 +1,87 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
namespace PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an XSigAnalogToken
|
||||||
|
/// </summary>
|
||||||
|
public sealed class XSigAnalogToken : XSigToken, IFormattable
|
||||||
{
|
{
|
||||||
|
private readonly ushort _value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an XSigAnalogToken
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class XSigAnalogToken : XSigToken, IFormattable
|
/// <param name="index"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public XSigAnalogToken(int index, ushort value)
|
||||||
|
: base(index)
|
||||||
{
|
{
|
||||||
private readonly ushort _value;
|
// 10-bits available for analog encoded data
|
||||||
|
if (index >= 1024 || index < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
|
||||||
/// <summary>
|
_value = value;
|
||||||
/// Constructor
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
public XSigAnalogToken(int index, ushort value)
|
|
||||||
: base(index)
|
|
||||||
{
|
|
||||||
// 10-bits available for analog encoded data
|
|
||||||
if (index >= 1024 || index < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("index");
|
|
||||||
|
|
||||||
_value = value;
|
/// <summary>
|
||||||
}
|
///
|
||||||
|
/// </summary>
|
||||||
|
public ushort Value
|
||||||
|
{
|
||||||
|
get { return _value; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Value
|
public override XSigTokenType TokenType
|
||||||
{
|
{
|
||||||
get { return _value; }
|
get { return XSigTokenType.Analog; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override XSigTokenType TokenType
|
/// <returns></returns>
|
||||||
{
|
public override byte[] GetBytes()
|
||||||
get { return XSigTokenType.Analog; }
|
{
|
||||||
}
|
return new[] {
|
||||||
|
(byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)),
|
||||||
|
(byte)((Index - 1) & 0x7F),
|
||||||
|
(byte)((Value & 0x3F80) >> 7),
|
||||||
|
(byte)(Value & 0x7F)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="offset"></param>
|
||||||
public override byte[] GetBytes()
|
/// <returns></returns>
|
||||||
{
|
public override XSigToken GetTokenWithOffset(int offset)
|
||||||
return new[] {
|
{
|
||||||
(byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)),
|
if (offset == 0) return this;
|
||||||
(byte)((Index - 1) & 0x7F),
|
return new XSigAnalogToken(Index + offset, Value);
|
||||||
(byte)((Value & 0x3F80) >> 7),
|
}
|
||||||
(byte)(Value & 0x7F)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offset"></param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
public override string ToString()
|
||||||
/// <summary>
|
{
|
||||||
/// GetTokenWithOffset method
|
return Index + " = 0x" + Value.ToString("X4");
|
||||||
/// </summary>
|
}
|
||||||
public override XSigToken GetTokenWithOffset(int offset)
|
|
||||||
{
|
|
||||||
if (offset == 0) return this;
|
|
||||||
return new XSigAnalogToken(Index + offset, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="format"></param>
|
||||||
/// <summary>
|
/// <param name="formatProvider"></param>
|
||||||
/// ToString method
|
/// <returns></returns>
|
||||||
/// </summary>
|
public string ToString(string format, IFormatProvider formatProvider)
|
||||||
/// <inheritdoc />
|
{
|
||||||
public override string ToString()
|
return Value.ToString(format, formatProvider);
|
||||||
{
|
|
||||||
return Index + " = 0x" + Value.ToString("X4");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"></param>
|
|
||||||
/// <param name="formatProvider"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// ToString method
|
|
||||||
/// </summary>
|
|
||||||
public string ToString(string format, IFormatProvider formatProvider)
|
|
||||||
{
|
|
||||||
return Value.ToString(format, formatProvider);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,95 +1,84 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
namespace PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an XSigDigitalToken
|
||||||
|
/// </summary>
|
||||||
|
public sealed class XSigDigitalToken : XSigToken
|
||||||
{
|
{
|
||||||
|
private readonly bool _value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an XSigDigitalToken
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class XSigDigitalToken : XSigToken
|
/// <param name="index"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public XSigDigitalToken(int index, bool value)
|
||||||
|
: base(index)
|
||||||
{
|
{
|
||||||
private readonly bool _value;
|
// 12-bits available for digital encoded data
|
||||||
|
if (index >= 4096 || index < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
|
||||||
/// <summary>
|
_value = value;
|
||||||
///
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
public XSigDigitalToken(int index, bool value)
|
|
||||||
: base(index)
|
|
||||||
{
|
|
||||||
// 12-bits available for digital encoded data
|
|
||||||
if (index >= 4096 || index < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("index");
|
|
||||||
|
|
||||||
_value = value;
|
/// <summary>
|
||||||
}
|
///
|
||||||
|
/// </summary>
|
||||||
|
public bool Value
|
||||||
|
{
|
||||||
|
get { return _value; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Value
|
public override XSigTokenType TokenType
|
||||||
{
|
{
|
||||||
get { return _value; }
|
get { return XSigTokenType.Digital; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override XSigTokenType TokenType
|
/// <returns></returns>
|
||||||
{
|
public override byte[] GetBytes()
|
||||||
get { return XSigTokenType.Digital; }
|
{
|
||||||
}
|
return new[] {
|
||||||
|
(byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)),
|
||||||
|
(byte)((Index - 1) & 0x7F)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="offset"></param>
|
||||||
public override byte[] GetBytes()
|
/// <returns></returns>
|
||||||
{
|
public override XSigToken GetTokenWithOffset(int offset)
|
||||||
return new[] {
|
{
|
||||||
(byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)),
|
if (offset == 0) return this;
|
||||||
(byte)((Index - 1) & 0x7F)
|
return new XSigDigitalToken(Index + offset, Value);
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offset"></param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
public override string ToString()
|
||||||
/// <summary>
|
{
|
||||||
/// GetTokenWithOffset method
|
return Index + " = " + (Value ? "High" : "Low");
|
||||||
/// </summary>
|
}
|
||||||
public override XSigToken GetTokenWithOffset(int offset)
|
|
||||||
{
|
|
||||||
if (offset == 0) return this;
|
|
||||||
return new XSigDigitalToken(Index + offset, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="formatProvider"></param>
|
||||||
/// <summary>
|
/// <returns></returns>
|
||||||
/// ToString method
|
public string ToString(IFormatProvider formatProvider)
|
||||||
/// </summary>
|
{
|
||||||
/// <inheritdoc />
|
return Value.ToString(formatProvider);
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Index + " = " + (Value ? "High" : "Low");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formatProvider"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// ToString method
|
|
||||||
/// </summary>
|
|
||||||
public string ToString(IFormatProvider formatProvider)
|
|
||||||
{
|
|
||||||
return Value.ToString(formatProvider);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,88 +1,80 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
namespace PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an XSigSerialToken
|
||||||
|
/// </summary>
|
||||||
|
public sealed class XSigSerialToken : XSigToken
|
||||||
{
|
{
|
||||||
|
private readonly string _value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an XSigSerialToken
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class XSigSerialToken : XSigToken
|
/// <param name="index"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public XSigSerialToken(int index, string value)
|
||||||
|
: base(index)
|
||||||
{
|
{
|
||||||
private readonly string _value;
|
// 10-bits available for serial encoded data
|
||||||
|
if (index >= 1024 || index < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
|
||||||
/// <summary>
|
_value = value;
|
||||||
/// Constructor
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
public XSigSerialToken(int index, string value)
|
|
||||||
: base(index)
|
|
||||||
{
|
|
||||||
// 10-bits available for serial encoded data
|
|
||||||
if (index >= 1024 || index < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("index");
|
|
||||||
|
|
||||||
_value = value;
|
/// <summary>
|
||||||
}
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string Value
|
||||||
|
{
|
||||||
|
get { return _value; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Value
|
public override XSigTokenType TokenType
|
||||||
{
|
{
|
||||||
get { return _value; }
|
get { return XSigTokenType.Serial; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override XSigTokenType TokenType
|
/// <returns></returns>
|
||||||
{
|
public override byte[] GetBytes()
|
||||||
get { return XSigTokenType.Serial; }
|
{
|
||||||
}
|
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;
|
||||||
|
|
||||||
/// <summary>
|
Buffer.BlockCopy(serialBytes, 0, xsig, 2, serialBytes.Length);
|
||||||
///
|
return xsig;
|
||||||
/// </summary>
|
}
|
||||||
/// <returns></returns>
|
|
||||||
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;
|
|
||||||
|
|
||||||
Buffer.BlockCopy(serialBytes, 0, xsig, 2, serialBytes.Length);
|
/// <summary>
|
||||||
return xsig;
|
///
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override XSigToken GetTokenWithOffset(int offset)
|
||||||
|
{
|
||||||
|
if (offset == 0) return this;
|
||||||
|
return new XSigSerialToken(Index + offset, Value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offset"></param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
public override string ToString()
|
||||||
/// <summary>
|
{
|
||||||
/// GetTokenWithOffset method
|
return Index + " = \"" + Value + "\"";
|
||||||
/// </summary>
|
|
||||||
public override XSigToken GetTokenWithOffset(int offset)
|
|
||||||
{
|
|
||||||
if (offset == 0) return this;
|
|
||||||
return new XSigSerialToken(Index + offset, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <summary>
|
|
||||||
/// ToString method
|
|
||||||
/// </summary>
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Index + " = \"" + Value + "\"";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,45 +1,44 @@
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
namespace PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the base class for all XSig datatypes.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class XSigToken
|
||||||
{
|
{
|
||||||
|
private readonly int _index;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the base class for all XSig datatypes.
|
/// Constructs an XSigToken with the specified index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class XSigToken
|
/// <param name="index">Index for the data.</param>
|
||||||
|
protected XSigToken(int index)
|
||||||
{
|
{
|
||||||
private readonly int _index;
|
_index = index;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs an XSigToken with the specified index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index for the data.</param>
|
|
||||||
protected XSigToken(int index)
|
|
||||||
{
|
|
||||||
_index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// XSig 1-based index.
|
|
||||||
/// </summary>
|
|
||||||
public int Index
|
|
||||||
{
|
|
||||||
get { return _index; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken type.
|
|
||||||
/// </summary>
|
|
||||||
public abstract XSigTokenType TokenType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates the XSig bytes for the corresponding token.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>XSig byte array.</returns>
|
|
||||||
public abstract byte[] GetBytes();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a new token if necessary with an updated index based on the specified offset.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset">Offset to adjust the index with.</param>
|
|
||||||
/// <returns>XSigToken</returns>
|
|
||||||
public abstract XSigToken GetTokenWithOffset(int offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// XSig 1-based index.
|
||||||
|
/// </summary>
|
||||||
|
public int Index
|
||||||
|
{
|
||||||
|
get { return _index; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// XSigToken type.
|
||||||
|
/// </summary>
|
||||||
|
public abstract XSigTokenType TokenType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the XSig bytes for the corresponding token.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>XSig byte array.</returns>
|
||||||
|
public abstract byte[] GetBytes();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a new token if necessary with an updated index based on the specified offset.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Offset to adjust the index with.</param>
|
||||||
|
/// <returns>XSigToken</returns>
|
||||||
|
public abstract XSigToken GetTokenWithOffset(int offset);
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue