mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-10 10:15:01 +00:00
Compare commits
1 Commits
v2.2.2
...
feature/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf27e995c2 |
@@ -1,22 +0,0 @@
|
|||||||
name: Build PepperDash Essentials
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
getVersion:
|
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
|
||||||
secrets: inherit
|
|
||||||
build-4Series:
|
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
|
||||||
secrets: inherit
|
|
||||||
needs: getVersion
|
|
||||||
if: needs.getVersion.outputs.newVersion == 'true'
|
|
||||||
with:
|
|
||||||
newVersion: ${{ needs.getVersion.outputs.newVersion }}
|
|
||||||
version: ${{ needs.getVersion.outputs.version }}
|
|
||||||
tag: ${{ needs.getVersion.outputs.tag }}
|
|
||||||
channel: ${{ needs.getVersion.outputs.channel }}
|
|
||||||
bypassPackageCheck: true
|
|
||||||
96
.github/workflows/docker.yml
vendored
Normal file
96
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
name: Branch Build Using Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- feature-2.0.0/*
|
||||||
|
- hotfix-2.0.0/*
|
||||||
|
- release-2.0.0/*
|
||||||
|
- development-2.0.0
|
||||||
|
|
||||||
|
env:
|
||||||
|
# solution path doesn't need slashes unless there it is multiple folders deep
|
||||||
|
# solution name does not include extension. .sln is assumed
|
||||||
|
SOLUTION_PATH: .
|
||||||
|
SOLUTION_FILE: PepperDash.Essentials
|
||||||
|
# Do not edit this, we're just creating it here
|
||||||
|
VERSION: 0.0.0-buildtype-buildnumber
|
||||||
|
# Defaults to debug for build type
|
||||||
|
BUILD_TYPE: Debug
|
||||||
|
# Defaults to main as the release branch. Change as necessary
|
||||||
|
RELEASE_BRANCH: main
|
||||||
|
jobs:
|
||||||
|
Build_Project_4-Series:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set Version Number
|
||||||
|
id: setVersion
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$latestVersion = [version]"2.0.0"
|
||||||
|
|
||||||
|
$newVersion = [version]$latestVersion
|
||||||
|
$phase = ""
|
||||||
|
$newVersionString = ""
|
||||||
|
|
||||||
|
switch -regex ($Env:GITHUB_REF) {
|
||||||
|
'^refs\/pull\/*.' {
|
||||||
|
$phase = 'beta';
|
||||||
|
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||||
|
}
|
||||||
|
'^refs\/heads\/hotfix-2.0.0\/*.' {
|
||||||
|
$phase = 'hotfix'
|
||||||
|
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||||
|
}
|
||||||
|
'^refs\/heads\/release-2.0.0\/*.' {
|
||||||
|
$splitRef = $Env:GITHUB_REF -split "/"
|
||||||
|
$version = [version]($splitRef[-1] -replace "v", "")
|
||||||
|
$phase = 'rc'
|
||||||
|
$newVersionString = "{0}-{1}-{2}" -f $version, $phase, $Env:GITHUB_RUN_NUMBER
|
||||||
|
}
|
||||||
|
'^refs\/heads\/feature-2.0.0\/*.' {
|
||||||
|
$phase = 'alpha'
|
||||||
|
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||||
|
}
|
||||||
|
'development-2.0.0' {
|
||||||
|
$phase = 'beta'
|
||||||
|
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "version=$newVersionString" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||||
|
- name: Setup MS Build
|
||||||
|
uses: microsoft/setup-msbuild@v1.1
|
||||||
|
- name: restore Nuget Packages
|
||||||
|
run: nuget restore .\$($Env:SOLUTION_FILE).sln
|
||||||
|
# Build the solutions in the docker image
|
||||||
|
- name: Build Solution
|
||||||
|
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
|
||||||
|
- name: Pack Solution
|
||||||
|
run: dotnet pack .\$($Env:SOLUTION_FILE).sln --configuration $env:BUILD_TYPE --output ./output /p:Version="${{ steps.setVersion.outputs.version }}"
|
||||||
|
- name: Create tag for non-rc builds
|
||||||
|
if: contains(steps.setVersion.outputs.version, 'alpha')
|
||||||
|
run: |
|
||||||
|
git tag ${{ steps.setVersion.outputs.version }}
|
||||||
|
git push --tags origin
|
||||||
|
# Create the release on the source repo
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
# if: contains(steps.setVersion.outputs.version,'-rc-') ||
|
||||||
|
# contains(steps.setVersion.outputs.version,'-hotfix-') ||
|
||||||
|
# contains(steps.setVersion.outputs.version, '-beta-')
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: 'output\**\*.*(cpz|cplz)'
|
||||||
|
generateReleaseNotes: true
|
||||||
|
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
|
||||||
|
tag: ${{ steps.setVersion.outputs.version }}
|
||||||
|
- name: Setup Nuget
|
||||||
|
run: |
|
||||||
|
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
|
||||||
|
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
|
||||||
|
- name: Publish to Nuget
|
||||||
|
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||||
|
- name: Publish to Github Nuget
|
||||||
|
run: nuget push .\output\*.nupkg -Source github
|
||||||
57
.github/workflows/main.yml
vendored
Normal file
57
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: main Build using Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
branches:
|
||||||
|
- main-2.0.0
|
||||||
|
env:
|
||||||
|
# solution path doesn't need slashes unless there it is multiple folders deep
|
||||||
|
# solution name does not include extension. .sln is assumed
|
||||||
|
SOLUTION_PATH: PepperDashEssentials
|
||||||
|
SOLUTION_FILE: PepperDashEssentials
|
||||||
|
# Do not edit this, we're just creating it here
|
||||||
|
VERSION: 0.0.0-buildtype-buildnumber
|
||||||
|
# Defaults to debug for build type
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
# Defaults to main as the release branch. Change as necessary
|
||||||
|
RELEASE_BRANCH: main
|
||||||
|
jobs:
|
||||||
|
Build_Project:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
# First we checkout the source repo
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
# Generate the appropriate version number
|
||||||
|
- name: Set Version Number
|
||||||
|
shell: powershell
|
||||||
|
id: setVersion
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||||
|
run: echo "VERSION=$($Env:TAG_NAME)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||||
|
- name: Setup MS Build
|
||||||
|
uses: microsoft/setup-msbuild@v1.1
|
||||||
|
- name: restore Nuget Packages
|
||||||
|
run: nuget restore .\$($Env:SOLUTION_FILE).sln
|
||||||
|
- name: Build Solution
|
||||||
|
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
|
||||||
|
- name: Pack Solution
|
||||||
|
run: dotnet pack .\$($Env:SOLUTION_FILE).sln --configuration $env:BUILD_TYPE --output ./output /p:Version="${{ steps.setVersion.outputs.version }}"
|
||||||
|
- name: Upload Release
|
||||||
|
id: create_release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
updateRelease: true
|
||||||
|
artifacts: 'output\**\*.*(cpz|cplz)'
|
||||||
|
tag: ${{ steps.setVersion.outputs.version }}
|
||||||
|
- name: Setup Nuget
|
||||||
|
run: |
|
||||||
|
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
|
||||||
|
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
|
||||||
|
- name: Publish to Nuget
|
||||||
|
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||||
|
- name: Publish to Github Nuget
|
||||||
|
run: nuget push .\output\*.nupkg -Source github
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Essentials-Template-UI"]
|
||||||
|
path = Essentials-Template-UI
|
||||||
|
url = https://github.com/PepperDash/Essentials-Template-UI.git
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
[
|
|
||||||
"@semantic-release/commit-analyzer",
|
|
||||||
{
|
|
||||||
"releaseRules": [
|
|
||||||
{ "scope": "force-patch", "release": "patch" },
|
|
||||||
{ "scope": "no-release", "release": false }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@semantic-release/release-notes-generator",
|
|
||||||
["@semantic-release/changelog",
|
|
||||||
{
|
|
||||||
"changelogFile": "CHANGELOG.md"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"@semantic-release/exec",
|
|
||||||
{
|
|
||||||
"verifyReleaseCmd": "echo \"newVersion=true\" >> $GITHUB_OUTPUT",
|
|
||||||
"publishCmd": "echo \"version=${nextRelease.version}\" >> $GITHUB_OUTPUT && echo \"tag=${nextRelease.gitTag}\" >> $GITHUB_OUTPUT && echo \"type=${nextRelease.type}\" >> $GITHUB_OUTPUT && echo \"channel=${nextRelease.channel}\" >> $GITHUB_OUTPUT"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"branches": [
|
|
||||||
"main",
|
|
||||||
{"name": "development", "prerelease": "beta", "channel": "beta"},
|
|
||||||
{"name": "release", "prerelease": "rc", "channel": "rc"},
|
|
||||||
{
|
|
||||||
"name": "replace-me-feature-branch",
|
|
||||||
"prerelease": "replace-me-prerelease",
|
|
||||||
"channel": "replace-me-prerelease"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -91,8 +91,8 @@ we receive and the availability of resources to evaluate contributions, we antic
|
|||||||
project remains dynamic and relevant. This may affect our responsiveness and ability to accept pull requests
|
project remains dynamic and relevant. This may affect our responsiveness and ability to accept pull requests
|
||||||
quickly. This does not mean we are ignoring them.
|
quickly. This does not mean we are ignoring them.
|
||||||
- Not all innovative ideas need to be accepted as pull requests into this GitHub project to be valuable to the community.
|
- Not all innovative ideas need to be accepted as pull requests into this GitHub project to be valuable to the community.
|
||||||
There may be times when we recommend that you just share your code for some enhancement to Essentials from your own
|
There may be times when we recommend that you just share your code for some enhancement to Ghidra from your own
|
||||||
repository. As we identify and recognize extensions that are of general interest to Essentials, we
|
repository. As we identify and recognize extensions that are of general interest to the reverse engineering community, we
|
||||||
may seek to incorporate them with our baseline.
|
may seek to incorporate them with our baseline.
|
||||||
|
|
||||||
## Legal
|
## Legal
|
||||||
|
|||||||
1
Essentials-Template-UI
Submodule
1
Essentials-Template-UI
Submodule
Submodule Essentials-Template-UI added at 8eaf88791b
@@ -1,97 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.4.33213.308
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Devices.Common", "src\PepperDash.Essentials.Devices.Common\PepperDash.Essentials.Devices.Common.csproj", "{53E204B7-97DD-441D-A96C-721DF014DF82}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials", "src\PepperDash.Essentials\PepperDash.Essentials.csproj", "{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core", "src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj", "{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile Control", "Mobile Control", "{B24989D7-32B5-48D5-9AE1-5F3B17D25206}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl", "src\PepperDash.Essentials.MobileControl\PepperDash.Essentials.MobileControl.csproj", "{F6D362DE-2256-44B1-927A-8CE4705D839A}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl.Messengers", "src\PepperDash.Essentials.MobileControl.Messengers\PepperDash.Essentials.MobileControl.Messengers.csproj", "{B438694F-8FF7-464A-9EC8-10427374471F}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Essentials", "Essentials", "{AD98B742-8D85-481C-A69D-D8D8ABED39EA}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{53E204B7-97DD-441D-A96C-721DF014DF82} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
|
||||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
|
||||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
|
||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
44
PepperDash.Essentials.sln
Normal file
44
PepperDash.Essentials.sln
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33213.308
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Devices.Common", "src\PepperDash.Essentials.Devices.Common\PepperDash.Essentials.Devices.Common.csproj", "{53E204B7-97DD-441D-A96C-721DF014DF82}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials", "src\PepperDash.Essentials\PepperDash.Essentials.csproj", "{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core", "src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj", "{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{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|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -61,3 +61,5 @@ For detailed documentation, see the [Wiki](https://github.com/PepperDash/Essenti
|
|||||||
## How-To (Getting Started)
|
## How-To (Getting Started)
|
||||||
|
|
||||||
See [Getting Started](https://github.com/PepperDash/Essentials/wiki/Get-started#how-to-get-started)
|
See [Getting Started](https://github.com/PepperDash/Essentials/wiki/Get-started#how-to-get-started)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>2.0.0-local</Version>
|
<Version>2.0.0-local</Version>
|
||||||
<InformationalVersion>$(Version)</InformationalVersion>
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
<Authors>PepperDash Technology</Authors>
|
<Authors>PepperDash Technologies</Authors>
|
||||||
<Company>PepperDash Technology</Company>
|
<Company>PepperDash Technologies</Company>
|
||||||
<Product>PepperDash Essentials</Product>
|
<Product>PepperDash Essentials</Product>
|
||||||
<Copyright>Copyright © 2025</Copyright>
|
<Copyright>Copyright © 2023</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>
|
||||||
|
|||||||
@@ -1,46 +1,26 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
|
<None Include="$(PackageOutputPath)\$(AssemblyName)\*.cpz" Condition="$(ProjectType) == 'Program'">
|
||||||
<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="$(PackageOutputPath)\$(AssemblyName)\*.cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Condition="$(ProjectType) == 'ProgramLibrary'">
|
<Target Name="Create CPLZ" AfterTargets="Build; AfterRebuild" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''">
|
||||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz</FileName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="$(ProjectType) == 'Program'">
|
|
||||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
|
|
||||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
|
|
||||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
|
||||||
</Delete>
|
|
||||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">
|
|
||||||
<Message Text="Creating CPLZ $(TargetDir)"></Message>
|
<Message Text="Creating CPLZ $(TargetDir)"></Message>
|
||||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))" />
|
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))" />
|
||||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
<MakeDir Directories="$(PackageOutputPath)\$(AssemblyName)" Condition="!Exists('$(PackageOutputPath)\$(AssemblyName)')" />
|
||||||
<Copy SourceFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" />
|
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
|
<Target Name="Copy CPZ NET6" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' == 'net6.0' ) Or ( '$(TargetFramework)' == 'net8.0' ))">
|
||||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
|
|
||||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
|
||||||
</Delete>
|
|
||||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CLZ" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Library')">
|
|
||||||
<Message Text="Copying CLZ"></Message>
|
|
||||||
<Move SourceFiles="$(TargetDir)\$(TargetName).clz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).$(TargetFramework).clz"/>
|
|
||||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).$(TargetFramework).clz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).clz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CPZ" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' ))">
|
|
||||||
<Message Text="Copying CPZ"></Message>
|
<Message Text="Copying CPZ"></Message>
|
||||||
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" />
|
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cpz" />
|
||||||
<Copy SourceFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cpz" />
|
</Target>
|
||||||
|
<Target Name="Copy CPZ NET47" AfterTargets="SimplSharpPostProcess47" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' ))">
|
||||||
|
<Message Text="Copying CPZ"></Message>
|
||||||
|
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cpz" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
using PepperDash.Core;
|
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the string event handler for line events on the gather
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
public delegate void LineReceivedHandler(string text);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attaches to IBasicCommunication as a text gather
|
|
||||||
/// </summary>
|
|
||||||
public class CommunicationGather
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when a line is received from the IBasicCommunication source.
|
|
||||||
/// The event merely contains the text, not an EventArgs type class.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> LineReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The communication port that this gathers on
|
|
||||||
/// </summary>
|
|
||||||
public ICommunicationReceiver Port { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default false. If true, the delimiter will be included in the line output
|
|
||||||
/// events
|
|
||||||
/// </summary>
|
|
||||||
public bool IncludeDelimiter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For receive buffer
|
|
||||||
/// </summary>
|
|
||||||
StringBuilder ReceiveBuffer = new StringBuilder();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delimiter, like it says!
|
|
||||||
/// </summary>
|
|
||||||
char Delimiter;
|
|
||||||
|
|
||||||
string[] StringDelimiters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for using a char delimiter
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="delimiter"></param>
|
|
||||||
public CommunicationGather(ICommunicationReceiver port, char delimiter)
|
|
||||||
{
|
|
||||||
Port = port;
|
|
||||||
Delimiter = delimiter;
|
|
||||||
port.TextReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(Port_TextReceived);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for using a single string delimiter
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="delimiter"></param>
|
|
||||||
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
|
||||||
:this(port, new string[] { delimiter} )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for using an array of string delimiters
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="delimiters"></param>
|
|
||||||
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
|
||||||
{
|
|
||||||
Port = port;
|
|
||||||
StringDelimiters = delimiters;
|
|
||||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
|
|
||||||
/// after the this call.
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
Port.TextReceived -= Port_TextReceived;
|
|
||||||
Port.TextReceived -= Port_TextReceivedStringDelimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for raw data coming from port
|
|
||||||
/// </summary>
|
|
||||||
void Port_TextReceived(object sender, GenericCommMethodReceiveTextArgs args)
|
|
||||||
{
|
|
||||||
var handler = LineReceived;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
ReceiveBuffer.Append(args.Text);
|
|
||||||
var str = ReceiveBuffer.ToString();
|
|
||||||
var lines = str.Split(Delimiter);
|
|
||||||
if (lines.Length > 0)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < lines.Length - 1; i++)
|
|
||||||
{
|
|
||||||
string strToSend = null;
|
|
||||||
if (IncludeDelimiter)
|
|
||||||
strToSend = lines[i] + Delimiter;
|
|
||||||
else
|
|
||||||
strToSend = lines[i];
|
|
||||||
handler(this, new GenericCommMethodReceiveTextArgs(strToSend));
|
|
||||||
}
|
|
||||||
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
void Port_TextReceivedStringDelimiter(object sender, GenericCommMethodReceiveTextArgs args)
|
|
||||||
{
|
|
||||||
var handler = LineReceived;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
// Receive buffer should either be empty or not contain the delimiter
|
|
||||||
// If the line does not have a delimiter, append the
|
|
||||||
ReceiveBuffer.Append(args.Text);
|
|
||||||
var str = ReceiveBuffer.ToString();
|
|
||||||
|
|
||||||
// Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a
|
|
||||||
|
|
||||||
// RX: DEV
|
|
||||||
// Split: (1) "DEV"
|
|
||||||
// RX: I
|
|
||||||
// Split: (1) "DEVI"
|
|
||||||
// RX: CE get version
|
|
||||||
// Split: (1) "DEVICE get version"
|
|
||||||
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
|
|
||||||
// Split: (2) DEVICE get version, +OK "value":"1234"
|
|
||||||
|
|
||||||
// Iterate the delimiters and fire an event for any matching delimiter
|
|
||||||
foreach (var delimiter in StringDelimiters)
|
|
||||||
{
|
|
||||||
var lines = Regex.Split(str, delimiter);
|
|
||||||
if (lines.Length == 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (int i = 0; i < lines.Length - 1; i++)
|
|
||||||
{
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deconstructor. Disconnects from port TextReceived events.
|
|
||||||
/// </summary>
|
|
||||||
~CommunicationGather()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using 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>
|
|
||||||
/// 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>
|
|
||||||
/// 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
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _DebugTimeoutInMs/60000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 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>
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The available settings for stream debugging response types
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum eStreamDebuggingDataTypeSettings
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in byte format
|
|
||||||
/// </summary>
|
|
||||||
Bytes = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in text format
|
|
||||||
/// </summary>
|
|
||||||
Text = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in both byte and text formats
|
|
||||||
/// </summary>
|
|
||||||
Both = Bytes | Text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Config properties that indicate how to communicate with a device for control
|
|
||||||
/// </summary>
|
|
||||||
public class ControlPropertiesConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The method of control
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("method")]
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
|
||||||
public eControlMethod Method { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the device that contains the control port
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string ControlPortDevKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of the control port on the device specified by ControlPortDevKey
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
|
||||||
public uint? ControlPortNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the control port on the device specified by ControlPortDevKey
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
|
||||||
public string ControlPortName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Properties for ethernet based communications
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The filename and path for the IR file
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string IrFile { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The IpId of a Crestron device
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string IpId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Readonly uint representation of the IpId
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Char indicating end of line
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public char EndOfLineChar { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to Environment.NewLine;
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string EndOfLineString { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string DeviceReadyResponsePattern { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used when communcating to programs running in VC-4
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public string RoomId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ControlPropertiesConfig()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/*PepperDash Technology Corp.
|
|
||||||
Copyright: 2017
|
|
||||||
------------------------------------
|
|
||||||
***Notice of Ownership and Copyright***
|
|
||||||
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
|
||||||
which claims copyright under the laws of the United States of America in the entire body of material
|
|
||||||
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
|
||||||
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
|
||||||
PepperDash Technology Corporation reserves all rights under applicable laws.
|
|
||||||
------------------------------------ */
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
|
|
||||||
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
/// 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>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ServerState State { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
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>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public object Socket { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="socket"></param>
|
|
||||||
/// <param name="clientStatus"></param>
|
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
|
||||||
{
|
|
||||||
Socket = socket;
|
|
||||||
ClientStatus = clientStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="socket"></param>
|
|
||||||
/// <param name="clientIndex"></param>
|
|
||||||
/// <param name="clientStatus"></param>
|
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
|
||||||
{
|
|
||||||
Socket = socket;
|
|
||||||
ReceivedFromClientIndex = clientIndex;
|
|
||||||
ClientStatus = clientStatus;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// EventArgs for TCP server com method receive text
|
|
||||||
/// </summary>
|
|
||||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort ReceivedFromClientIndexShort
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return (ushort)ReceivedFromClientIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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>
|
|
||||||
/// EventArgs for TCP server client ready for communication
|
|
||||||
/// </summary>
|
|
||||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool IsReady;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isReady"></param>
|
|
||||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
|
||||||
{
|
|
||||||
IsReady = isReady;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// EventArgs for UDP connected
|
|
||||||
/// </summary>
|
|
||||||
public class GenericUdpConnectedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,955 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class to handle secure TCP/IP communications with a server
|
|
||||||
/// </summary>
|
|
||||||
public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
|
||||||
{
|
|
||||||
private const string SplusKey = "Uninitialized Secure Tcp _client";
|
|
||||||
/// <summary>
|
|
||||||
/// Stream debugging
|
|
||||||
/// </summary>
|
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires when data is received from the server and returns it as a Byte array
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires when data is received from the server and returns it as text
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
#region GenericSecureTcpIpClient Events & Delegates
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auto reconnect evant handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler AutoReconnectTriggered;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread.
|
|
||||||
/// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require
|
|
||||||
/// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
#region GenricTcpIpClient properties
|
|
||||||
|
|
||||||
private string _hostname;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname
|
|
||||||
{
|
|
||||||
get { return _hostname; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_hostname = value;
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
_client.AddressClientConnectedTo = _hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper
|
|
||||||
/// </summary>
|
|
||||||
public ushort UPort
|
|
||||||
{
|
|
||||||
get { return Convert.ToUInt16(Port); }
|
|
||||||
set { Port = Convert.ToInt32(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal secure client
|
|
||||||
/// </summary>
|
|
||||||
private SecureTCPClient _client;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is connected
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// _client socket status Read only
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected would be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status text shows the message associated with socket status
|
|
||||||
/// </summary>
|
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connection failure reason
|
|
||||||
/// </summary>
|
|
||||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag Set only when the disconnect method is called.
|
|
||||||
/// </summary>
|
|
||||||
bool DisconnectCalledByUser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool Connected
|
|
||||||
{
|
|
||||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// private Timer for auto reconnect
|
|
||||||
private CTimer RetryTimer;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region GenericSecureTcpIpClient properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
|
||||||
/// </summary>
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for requires shared key bool
|
|
||||||
/// </summary>
|
|
||||||
public ushort USharedKeyRequired
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == 1)
|
|
||||||
SharedKeyRequired = true;
|
|
||||||
else
|
|
||||||
SharedKeyRequired = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// flag to show the client is waiting for the server to send the shared key
|
|
||||||
/// </summary>
|
|
||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Semaphore on connect method
|
|
||||||
/// </summary>
|
|
||||||
bool IsTryingToConnect;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is ready for communication after shared key exchange
|
|
||||||
/// </summary>
|
|
||||||
public bool IsReadyForCommunication { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsReadyForCommunication
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsReadyForCommunication
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool Heartbeat Enabled flag
|
|
||||||
/// </summary>
|
|
||||||
public bool HeartbeatEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for Heartbeat Enabled
|
|
||||||
/// </summary>
|
|
||||||
public ushort UHeartbeatEnabled
|
|
||||||
{
|
|
||||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
|
||||||
set { HeartbeatEnabled = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Heartbeat String
|
|
||||||
/// </summary>
|
|
||||||
public string HeartbeatString { get; set; }
|
|
||||||
//public int HeartbeatInterval = 50000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
|
||||||
/// </summary>
|
|
||||||
public int HeartbeatInterval { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simpl+ Heartbeat Analog value in seconds
|
|
||||||
/// </summary>
|
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
|
||||||
|
|
||||||
CTimer HeartbeatSendTimer;
|
|
||||||
CTimer HeartbeatAckTimer;
|
|
||||||
|
|
||||||
// Used to force disconnection on a dead connect attempt
|
|
||||||
CTimer ConnectFailTimer;
|
|
||||||
CTimer WaitForSharedKey;
|
|
||||||
private int ConnectionCount;
|
|
||||||
|
|
||||||
bool ProgramIsStopping;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue lock
|
|
||||||
/// </summary>
|
|
||||||
CCriticalSection DequeueLock = new CCriticalSection();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
|
||||||
/// calling initialize.
|
|
||||||
/// </summary>
|
|
||||||
public int ReceiveQueueSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before
|
|
||||||
/// calling initialize.
|
|
||||||
/// </summary>
|
|
||||||
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bufferSize"></param>
|
|
||||||
public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
|
||||||
Hostname = address;
|
|
||||||
Port = port;
|
|
||||||
BufferSize = bufferSize;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="clientConfigObject"></param>
|
|
||||||
public GenericSecureTcpIpClient(string key, TcpClientConfigObject clientConfigObject)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
|
|
||||||
Initialize(clientConfigObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor for S+
|
|
||||||
/// </summary>
|
|
||||||
public GenericSecureTcpIpClient()
|
|
||||||
: base(SplusKey)
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Just to help S+ set the key
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="config"></param>
|
|
||||||
public void Initialize(TcpClientConfigObject config)
|
|
||||||
{
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Could not initialize client with key: {0}", Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Hostname = config.Control.TcpSshProperties.Address;
|
|
||||||
Port = config.Control.TcpSshProperties.Port > 0 && config.Control.TcpSshProperties.Port <= 65535
|
|
||||||
? config.Control.TcpSshProperties.Port
|
|
||||||
: 80;
|
|
||||||
|
|
||||||
AutoReconnect = config.Control.TcpSshProperties.AutoReconnect;
|
|
||||||
AutoReconnectIntervalMs = config.Control.TcpSshProperties.AutoReconnectIntervalMs > 1000
|
|
||||||
? config.Control.TcpSshProperties.AutoReconnectIntervalMs
|
|
||||||
: 5000;
|
|
||||||
|
|
||||||
SharedKey = config.SharedKey;
|
|
||||||
SharedKeyRequired = config.SharedKeyRequired;
|
|
||||||
|
|
||||||
HeartbeatEnabled = config.HeartbeatRequired;
|
|
||||||
HeartbeatRequiredIntervalInSeconds = config.HeartbeatRequiredIntervalInSeconds > 0
|
|
||||||
? config.HeartbeatRequiredIntervalInSeconds
|
|
||||||
: (ushort)15;
|
|
||||||
|
|
||||||
|
|
||||||
HeartbeatString = string.IsNullOrEmpty(config.HeartbeatStringToMatch)
|
|
||||||
? "heartbeat"
|
|
||||||
: config.HeartbeatStringToMatch;
|
|
||||||
|
|
||||||
BufferSize = config.Control.TcpSshProperties.BufferSize > 2000
|
|
||||||
? config.Control.TcpSshProperties.BufferSize
|
|
||||||
: 2000;
|
|
||||||
|
|
||||||
ReceiveQueueSize = config.ReceiveQueueSize > 20
|
|
||||||
? config.ReceiveQueueSize
|
|
||||||
: 20;
|
|
||||||
|
|
||||||
MessageQueue = new CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing _client connection");
|
|
||||||
ProgramIsStopping = true;
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deactivate the client
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override bool Deactivate()
|
|
||||||
{
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
|
||||||
DisconnectClient();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
ConnectionCount++;
|
|
||||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsTryingToConnect = true;
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Port < 1 || Port > 65535)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up previous client
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
|
|
||||||
_client = new SecureTCPClient(Hostname, Port, BufferSize);
|
|
||||||
_client.SocketStatusChange += Client_SocketStatusChange;
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
_client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
|
|
||||||
_client.AddressClientConnectedTo = Hostname;
|
|
||||||
_client.PortNumber = Port;
|
|
||||||
// SecureClient = c;
|
|
||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
|
||||||
|
|
||||||
ConnectFailTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
//if (ConnectionHasHungCallback != null)
|
|
||||||
//{
|
|
||||||
// ConnectionHasHungCallback();
|
|
||||||
//}
|
|
||||||
//SecureClient.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
|
||||||
_client.ConnectToServerAsync(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
|
||||||
{
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
}
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
|
||||||
o.ReceiveDataAsync(Receive);
|
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
|
||||||
{
|
|
||||||
WaitingForSharedKeyResponse = true;
|
|
||||||
WaitForSharedKey = new CTimer(timer =>
|
|
||||||
{
|
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
|
||||||
o.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//CLient connected and shared key is not required so just raise the ready for communication event. if Shared key
|
|
||||||
//required this is called by the shared key being negotiated
|
|
||||||
if (IsReadyForCommunication == false)
|
|
||||||
{
|
|
||||||
OnClientReadyForcommunications(true); // Key not required
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "_client connection exception: {0}", ex.Message);
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
this.LogVerbose("Disconnect Called");
|
|
||||||
|
|
||||||
DisconnectCalledByUser = true;
|
|
||||||
|
|
||||||
// stop trying reconnects, if we are
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
DisconnectClient();
|
|
||||||
this.LogDebug("Disconnected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does the actual disconnect business
|
|
||||||
/// </summary>
|
|
||||||
public void DisconnectClient()
|
|
||||||
{
|
|
||||||
if (_client == null) return;
|
|
||||||
|
|
||||||
Debug.Console(1, this, "Disconnecting client");
|
|
||||||
if (IsConnected)
|
|
||||||
_client.DisconnectFromServer();
|
|
||||||
|
|
||||||
// close up client. ALWAYS use this when disconnecting.
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
|
||||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
|
||||||
_client.Dispose();
|
|
||||||
_client = null;
|
|
||||||
|
|
||||||
if (ConnectFailTimer == null) return;
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
ConnectFailTimer.Dispose();
|
|
||||||
ConnectFailTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from Connect failure or Socket Status change if
|
|
||||||
/// auto reconnect and socket disconnected (Not disconnected by user)
|
|
||||||
/// </summary>
|
|
||||||
void CheckClosedAndTryReconnect()
|
|
||||||
{
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
|
||||||
{
|
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
|
||||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
if (AutoReconnectTriggered != null)
|
|
||||||
AutoReconnectTriggered(this, new EventArgs());
|
|
||||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
void Receive(SecureTCPClient client, int numBytes)
|
|
||||||
{
|
|
||||||
if (numBytes > 0)
|
|
||||||
{
|
|
||||||
string str = string.Empty;
|
|
||||||
var handler = TextReceivedQueueInvoke;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
|
||||||
Debug.Console(2, this, "_client Received:\r--------\r{0}\r--------", str);
|
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
|
||||||
{
|
|
||||||
|
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
|
||||||
SendText(SharedKey + "\n");
|
|
||||||
}
|
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
|
||||||
{
|
|
||||||
StopWaitForSharedKeyTimer();
|
|
||||||
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//var bytesHandler = BytesReceived;
|
|
||||||
//if (bytesHandler != null)
|
|
||||||
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
|
||||||
}
|
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
client.ReceiveDataAsync(Receive);
|
|
||||||
|
|
||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var gotLock = DequeueLock.TryEnter();
|
|
||||||
if (gotLock)
|
|
||||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
|
||||||
{
|
|
||||||
client.DisconnectFromServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
|
||||||
/// </summary>
|
|
||||||
void DequeueEvent()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather.
|
|
||||||
var message = MessageQueue.Dequeue();
|
|
||||||
var handler = TextReceivedQueueInvoke;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
handler(this, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
this.LogException(e, "DequeueEvent error");
|
|
||||||
}
|
|
||||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
|
||||||
if (DequeueLock != null)
|
|
||||||
{
|
|
||||||
DequeueLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeartbeatStart()
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
this.LogVerbose("Starting Heartbeat");
|
|
||||||
if (HeartbeatSendTimer == null)
|
|
||||||
{
|
|
||||||
|
|
||||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer == null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void HeartbeatStop()
|
|
||||||
{
|
|
||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
|
||||||
HeartbeatSendTimer.Stop();
|
|
||||||
HeartbeatSendTimer = null;
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
|
||||||
HeartbeatAckTimer.Stop();
|
|
||||||
HeartbeatAckTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void SendHeartbeat(object notused)
|
|
||||||
{
|
|
||||||
this.SendText(HeartbeatString);
|
|
||||||
Debug.Console(2, this, "Sending Heartbeat");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//private method to check heartbeat requirements and start or reset timer
|
|
||||||
string checkHeartbeat(string received)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(HeartbeatString))
|
|
||||||
{
|
|
||||||
var remainingText = received.Replace(HeartbeatString, "");
|
|
||||||
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
|
||||||
if (noDelimiter.Contains(HeartbeatString))
|
|
||||||
{
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
|
||||||
return remainingText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void HeartbeatAckTimerFail(object o)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ErrorLog.Error("Heartbeat timeout Error on _client: {0}, {1}", Key, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
void StopWaitForSharedKeyTimer()
|
|
||||||
{
|
|
||||||
if (WaitForSharedKey != null)
|
|
||||||
{
|
|
||||||
WaitForSharedKey.Stop();
|
|
||||||
WaitForSharedKey = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
_client.SendDataAsync(bytes, bytes.Length, (c, n) =>
|
|
||||||
{
|
|
||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
|
||||||
if (n <= 0)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
if (bytes.Length > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
_client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SocketStatusChange Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="clientSocketStatus"></param>
|
|
||||||
void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
|
|
||||||
{
|
|
||||||
if (ProgramIsStopping)
|
|
||||||
{
|
|
||||||
ProgramIsStopping = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
|
||||||
|
|
||||||
OnConnectionChange();
|
|
||||||
// The client could be null or disposed by this time...
|
|
||||||
if (_client == null || _client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
HeartbeatStop();
|
|
||||||
OnClientReadyForcommunications(false); // socket has gone low
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper for ConnectionChange event
|
|
||||||
/// </summary>
|
|
||||||
void OnConnectionChange()
|
|
||||||
{
|
|
||||||
var handler = ConnectionChange;
|
|
||||||
if (handler == null) return;
|
|
||||||
|
|
||||||
handler(this, new GenericSocketStatusChageEventArgs(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to fire ClientReadyForCommunications event
|
|
||||||
/// </summary>
|
|
||||||
void OnClientReadyForcommunications(bool isReady)
|
|
||||||
{
|
|
||||||
IsReadyForCommunication = isReady;
|
|
||||||
if (IsReadyForCommunication)
|
|
||||||
HeartbeatStart();
|
|
||||||
|
|
||||||
var handler = ClientReadyForCommunications;
|
|
||||||
if (handler == null) return;
|
|
||||||
|
|
||||||
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,909 +0,0 @@
|
|||||||
/*PepperDash Technology Corp.
|
|
||||||
JAG
|
|
||||||
Copyright: 2017
|
|
||||||
------------------------------------
|
|
||||||
***Notice of Ownership and Copyright***
|
|
||||||
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
|
||||||
which claims copyright under the laws of the United States of America in the entire body of material
|
|
||||||
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
|
||||||
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
|
||||||
PepperDash Technology Corporation reserves all rights under applicable laws.
|
|
||||||
------------------------------------ */
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic secure TCP/IP client for server
|
|
||||||
/// </summary>
|
|
||||||
public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Band aid delegate for choked server
|
|
||||||
/// </summary>
|
|
||||||
internal delegate void ConnectionHasHungCallbackDelegate();
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of text received
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of auto reconnect sequence triggered
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler AutoReconnectTriggered;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread.
|
|
||||||
/// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of socket status change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is something of a band-aid callback. If the client times out during the connection process, because the server
|
|
||||||
/// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help
|
|
||||||
/// keep a watch on the server and reset it if necessary.
|
|
||||||
/// </summary>
|
|
||||||
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require
|
|
||||||
/// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties & Variables
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper
|
|
||||||
/// </summary>
|
|
||||||
public ushort UPort
|
|
||||||
{
|
|
||||||
get { return Convert.ToUInt16(Port); }
|
|
||||||
set { Port = Convert.ToInt32(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
|
||||||
/// </summary>
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for requires shared key bool
|
|
||||||
/// </summary>
|
|
||||||
public ushort USharedKeyRequired
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == 1)
|
|
||||||
SharedKeyRequired = true;
|
|
||||||
else
|
|
||||||
SharedKeyRequired = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// flag to show the client is waiting for the server to send the shared key
|
|
||||||
/// </summary>
|
|
||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Semaphore on connect method
|
|
||||||
/// </summary>
|
|
||||||
bool IsTryingToConnect;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is connected
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is ready for communication after shared key exchange
|
|
||||||
/// </summary>
|
|
||||||
public bool IsReadyForCommunication { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsReadyForCommunication
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsReadyForCommunication
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client socket status Read only
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
return Client.ClientStatus;
|
|
||||||
else
|
|
||||||
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected would be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status text shows the message associated with socket status
|
|
||||||
/// </summary>
|
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag Set only when the disconnect method is called.
|
|
||||||
/// </summary>
|
|
||||||
bool DisconnectCalledByUser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// private Timer for auto reconnect
|
|
||||||
/// </summary>
|
|
||||||
CTimer RetryTimer;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool HeartbeatEnabled { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort UHeartbeatEnabled
|
|
||||||
{
|
|
||||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
|
||||||
set { HeartbeatEnabled = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string HeartbeatString { get; set; }
|
|
||||||
//public int HeartbeatInterval = 50000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
|
||||||
/// </summary>
|
|
||||||
public int HeartbeatInterval { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simpl+ Heartbeat Analog value in seconds
|
|
||||||
/// </summary>
|
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
|
||||||
|
|
||||||
CTimer HeartbeatSendTimer;
|
|
||||||
CTimer HeartbeatAckTimer;
|
|
||||||
/// <summary>
|
|
||||||
/// Used to force disconnection on a dead connect attempt
|
|
||||||
/// </summary>
|
|
||||||
CTimer ConnectFailTimer;
|
|
||||||
CTimer WaitForSharedKey;
|
|
||||||
private int ConnectionCount;
|
|
||||||
/// <summary>
|
|
||||||
/// Internal secure client
|
|
||||||
/// </summary>
|
|
||||||
SecureTCPClient Client;
|
|
||||||
|
|
||||||
bool ProgramIsStopping;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue lock
|
|
||||||
/// </summary>
|
|
||||||
CCriticalSection DequeueLock = new CCriticalSection();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
|
||||||
/// calling initialize.
|
|
||||||
/// </summary>
|
|
||||||
public int ReceiveQueueSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before
|
|
||||||
/// calling initialize.
|
|
||||||
/// </summary>
|
|
||||||
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bufferSize"></param>
|
|
||||||
public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
Hostname = address;
|
|
||||||
Port = port;
|
|
||||||
BufferSize = bufferSize;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for S+
|
|
||||||
/// </summary>
|
|
||||||
public GenericSecureTcpIpClient_ForServer()
|
|
||||||
: base("Uninitialized Secure Tcp Client For Server")
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="clientConfigObject"></param>
|
|
||||||
public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
Initialize(clientConfigObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Just to help S+ set the key
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clientConfigObject"></param>
|
|
||||||
public void Initialize(TcpClientConfigObject clientConfigObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (clientConfigObject != null)
|
|
||||||
{
|
|
||||||
var TcpSshProperties = clientConfigObject.Control.TcpSshProperties;
|
|
||||||
Hostname = TcpSshProperties.Address;
|
|
||||||
AutoReconnect = TcpSshProperties.AutoReconnect;
|
|
||||||
AutoReconnectIntervalMs = TcpSshProperties.AutoReconnectIntervalMs > 1000 ?
|
|
||||||
TcpSshProperties.AutoReconnectIntervalMs : 5000;
|
|
||||||
SharedKey = clientConfigObject.SharedKey;
|
|
||||||
SharedKeyRequired = clientConfigObject.SharedKeyRequired;
|
|
||||||
HeartbeatEnabled = clientConfigObject.HeartbeatRequired;
|
|
||||||
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
|
|
||||||
clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15;
|
|
||||||
HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch;
|
|
||||||
Port = TcpSshProperties.Port;
|
|
||||||
BufferSize = TcpSshProperties.BufferSize > 2000 ? TcpSshProperties.BufferSize : 2000;
|
|
||||||
ReceiveQueueSize = clientConfigObject.ReceiveQueueSize > 20 ? clientConfigObject.ReceiveQueueSize : 20;
|
|
||||||
MessageQueue = new CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ErrorLog.Error("Could not initialize client with key: {0}", Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
ErrorLog.Error("Could not initialize client with key: {0}", Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
|
||||||
ProgramIsStopping = true;
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
ConnectionCount++;
|
|
||||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsTryingToConnect = true;
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Port < 1 || Port > 65535)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up previous client
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
|
|
||||||
Client = new SecureTCPClient(Hostname, Port, BufferSize);
|
|
||||||
Client.SocketStatusChange += Client_SocketStatusChange;
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
|
|
||||||
Client.AddressClientConnectedTo = Hostname;
|
|
||||||
Client.PortNumber = Port;
|
|
||||||
// SecureClient = c;
|
|
||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
|
||||||
|
|
||||||
ConnectFailTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
//if (ConnectionHasHungCallback != null)
|
|
||||||
//{
|
|
||||||
// ConnectionHasHungCallback();
|
|
||||||
//}
|
|
||||||
//SecureClient.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
|
||||||
Client.ConnectToServerAsync(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
|
||||||
{
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
}
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
|
||||||
o.ReceiveDataAsync(Receive);
|
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
|
||||||
{
|
|
||||||
WaitingForSharedKeyResponse = true;
|
|
||||||
WaitForSharedKey = new CTimer(timer =>
|
|
||||||
{
|
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
|
||||||
o.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//CLient connected and shared key is not required so just raise the ready for communication event. if Shared key
|
|
||||||
//required this is called by the shared key being negotiated
|
|
||||||
if (IsReadyForCommunication == false)
|
|
||||||
{
|
|
||||||
OnClientReadyForcommunications(true); // Key not required
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
this.LogVerbose("Disconnect Called");
|
|
||||||
|
|
||||||
DisconnectCalledByUser = true;
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Client.DisconnectFromServer();
|
|
||||||
|
|
||||||
}
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal call to close up client. ALWAYS use this when disconnecting.
|
|
||||||
/// </summary>
|
|
||||||
void Cleanup()
|
|
||||||
{
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
//SecureClient.DisconnectFromServer();
|
|
||||||
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
|
||||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
|
||||||
Client.Dispose();
|
|
||||||
Client = null;
|
|
||||||
}
|
|
||||||
if (ConnectFailTimer != null)
|
|
||||||
{
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
ConnectFailTimer.Dispose();
|
|
||||||
ConnectFailTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>ff
|
|
||||||
/// Called from Connect failure or Socket Status change if
|
|
||||||
/// auto reconnect and socket disconnected (Not disconnected by user)
|
|
||||||
/// </summary>
|
|
||||||
void CheckClosedAndTryReconnect()
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
|
||||||
{
|
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
|
||||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
if(AutoReconnectTriggered != null)
|
|
||||||
AutoReconnectTriggered(this, new EventArgs());
|
|
||||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
void Receive(SecureTCPClient client, int numBytes)
|
|
||||||
{
|
|
||||||
if (numBytes > 0)
|
|
||||||
{
|
|
||||||
string str = string.Empty;
|
|
||||||
var handler = TextReceivedQueueInvoke;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
|
||||||
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
|
||||||
{
|
|
||||||
|
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
|
||||||
SendText(SharedKey + "\n");
|
|
||||||
}
|
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
|
||||||
{
|
|
||||||
StopWaitForSharedKeyTimer();
|
|
||||||
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//var bytesHandler = BytesReceived;
|
|
||||||
//if (bytesHandler != null)
|
|
||||||
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str));
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
|
||||||
}
|
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
client.ReceiveDataAsync(Receive);
|
|
||||||
|
|
||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var gotLock = DequeueLock.TryEnter();
|
|
||||||
if (gotLock)
|
|
||||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
|
||||||
{
|
|
||||||
client.DisconnectFromServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
|
||||||
/// </summary>
|
|
||||||
void DequeueEvent()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather.
|
|
||||||
var message = MessageQueue.Dequeue();
|
|
||||||
var handler = TextReceivedQueueInvoke;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
handler(this, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
this.LogException(e, "DequeueEvent error");
|
|
||||||
}
|
|
||||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
|
||||||
if (DequeueLock != null)
|
|
||||||
{
|
|
||||||
DequeueLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeartbeatStart()
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Starting Heartbeat");
|
|
||||||
if (HeartbeatSendTimer == null)
|
|
||||||
{
|
|
||||||
|
|
||||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer == null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void HeartbeatStop()
|
|
||||||
{
|
|
||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
|
||||||
HeartbeatSendTimer.Stop();
|
|
||||||
HeartbeatSendTimer = null;
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
|
||||||
HeartbeatAckTimer.Stop();
|
|
||||||
HeartbeatAckTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void SendHeartbeat(object notused)
|
|
||||||
{
|
|
||||||
this.SendText(HeartbeatString);
|
|
||||||
Debug.Console(2, this, "Sending Heartbeat");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//private method to check heartbeat requirements and start or reset timer
|
|
||||||
string checkHeartbeat(string received)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(HeartbeatString))
|
|
||||||
{
|
|
||||||
var remainingText = received.Replace(HeartbeatString, "");
|
|
||||||
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
|
||||||
if (noDelimiter.Contains(HeartbeatString))
|
|
||||||
{
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
|
||||||
return remainingText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void HeartbeatAckTimerFail(object o)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
void StopWaitForSharedKeyTimer()
|
|
||||||
{
|
|
||||||
if (WaitForSharedKey != null)
|
|
||||||
{
|
|
||||||
WaitForSharedKey.Stop();
|
|
||||||
WaitForSharedKey = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Client.SendDataAsync(bytes, bytes.Length, (c, n) =>
|
|
||||||
{
|
|
||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
|
||||||
if (n <= 0)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
if (bytes.Length > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
Client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SocketStatusChange Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="clientSocketStatus"></param>
|
|
||||||
void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
|
|
||||||
{
|
|
||||||
if (ProgramIsStopping)
|
|
||||||
{
|
|
||||||
ProgramIsStopping = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
|
||||||
|
|
||||||
OnConnectionChange();
|
|
||||||
// The client could be null or disposed by this time...
|
|
||||||
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
HeartbeatStop();
|
|
||||||
OnClientReadyForcommunications(false); // socket has gone low
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper for ConnectionChange event
|
|
||||||
/// </summary>
|
|
||||||
void OnConnectionChange()
|
|
||||||
{
|
|
||||||
var handler = ConnectionChange;
|
|
||||||
if (handler != null)
|
|
||||||
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to fire ClientReadyForCommunications event
|
|
||||||
/// </summary>
|
|
||||||
void OnClientReadyForcommunications(bool isReady)
|
|
||||||
{
|
|
||||||
IsReadyForCommunication = isReady;
|
|
||||||
if (this.IsReadyForCommunication) { HeartbeatStart(); }
|
|
||||||
var handler = ClientReadyForCommunications;
|
|
||||||
if (handler != null)
|
|
||||||
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,592 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using Org.BouncyCastle.Utilities;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
using Renci.SshNet;
|
|
||||||
using Renci.SshNet.Common;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
|
||||||
{
|
|
||||||
private const string SPlusKey = "Uninitialized SshClient";
|
|
||||||
/// <summary>
|
|
||||||
/// Object to enable stream debugging
|
|
||||||
/// </summary>
|
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when data is received. Delivers args with byte array
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that fires when data is received. Delivered as text.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event when the connection status changes.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
/////
|
|
||||||
///// </summary>
|
|
||||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Username for server
|
|
||||||
/// </summary>
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// And... Password for server. That was worth documenting!
|
|
||||||
/// </summary>
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True when the server is connected - when status == 2.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
// returns false if no client or not connected
|
|
||||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get { return _ClientStatus; }
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (_ClientStatus == value)
|
|
||||||
return;
|
|
||||||
_ClientStatus = value;
|
|
||||||
OnConnectionChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SocketStatus _ClientStatus;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected with be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)_ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether client will attempt reconnection on failure. Default is true
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will be set and unset by connect and disconnect only
|
|
||||||
/// </summary>
|
|
||||||
public bool ConnectEnabled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
|
||||||
/// Set to 5000 by default
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
SshClient Client;
|
|
||||||
|
|
||||||
ShellStream TheStream;
|
|
||||||
|
|
||||||
CTimer ReconnectTimer;
|
|
||||||
|
|
||||||
//Lock object to prevent simulatneous connect/disconnect operations
|
|
||||||
//private CCriticalSection connectLock = new CCriticalSection();
|
|
||||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
|
||||||
|
|
||||||
private bool DisconnectLogged = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Typical constructor.
|
|
||||||
/// </summary>
|
|
||||||
public GenericSshClient(string key, string hostname, int port, string username, string password) :
|
|
||||||
base(key)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
Key = key;
|
|
||||||
Hostname = hostname;
|
|
||||||
Port = port;
|
|
||||||
Username = username;
|
|
||||||
Password = password;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
|
|
||||||
ReconnectTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
if (ConnectEnabled)
|
|
||||||
{
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
}, System.Threading.Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor - Must set all properties before calling Connect
|
|
||||||
/// </summary>
|
|
||||||
public GenericSshClient()
|
|
||||||
: base(SPlusKey)
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
|
|
||||||
ReconnectTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
if (ConnectEnabled)
|
|
||||||
{
|
|
||||||
Connect();
|
|
||||||
}
|
|
||||||
}, System.Threading.Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
this.LogDebug("Program stopping. Closing connection");
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect to the server, using the provided properties.
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
// Don't go unless everything is here
|
|
||||||
if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535
|
|
||||||
|| Username == null || Password == null)
|
|
||||||
{
|
|
||||||
this.LogError("Connect failed. Check hostname, port, username and password are set or not null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectEnabled = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Wait();
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
this.LogDebug("Connection already connected. Exiting Connect");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.LogDebug("Attempting connect");
|
|
||||||
|
|
||||||
// Cancel reconnect if running.
|
|
||||||
if (ReconnectTimer != null)
|
|
||||||
{
|
|
||||||
ReconnectTimer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup the old client if it already exists
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
this.LogDebug("Cleaning up disconnected client");
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This handles both password and keyboard-interactive (like on OS-X, 'nixes)
|
|
||||||
KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username);
|
|
||||||
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
|
|
||||||
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
|
|
||||||
|
|
||||||
this.LogDebug("Creating new SshClient");
|
|
||||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
|
||||||
Client = new SshClient(connectionInfo);
|
|
||||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
|
||||||
|
|
||||||
//Attempt to connect
|
|
||||||
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Client.Connect();
|
|
||||||
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
|
|
||||||
if (TheStream.DataAvailable)
|
|
||||||
{
|
|
||||||
// empty the buffer if there is data
|
|
||||||
string str = TheStream.Read();
|
|
||||||
}
|
|
||||||
TheStream.DataReceived += Stream_DataReceived;
|
|
||||||
this.LogInformation("Connected");
|
|
||||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
|
||||||
DisconnectLogged = false;
|
|
||||||
}
|
|
||||||
catch (SshConnectionException e)
|
|
||||||
{
|
|
||||||
var ie = e.InnerException; // The details are inside!!
|
|
||||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
|
||||||
|
|
||||||
if (ie is SocketException)
|
|
||||||
{
|
|
||||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ie is System.Net.Sockets.SocketException socketException)
|
|
||||||
{
|
|
||||||
this.LogException(ie, "Connection failure: Cannot reach {host} on {port}",
|
|
||||||
Hostname, Port);
|
|
||||||
}
|
|
||||||
if (ie is SshAuthenticationException)
|
|
||||||
{
|
|
||||||
this.LogException(ie, "Authentication failure for username {userName}", Username);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.LogException(ie, "Error on connect");
|
|
||||||
|
|
||||||
DisconnectLogged = true;
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
|
||||||
if (AutoReconnect)
|
|
||||||
{
|
|
||||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
|
||||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(SshOperationTimeoutException ex)
|
|
||||||
{
|
|
||||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
|
||||||
|
|
||||||
DisconnectLogged = true;
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
|
||||||
if (AutoReconnect)
|
|
||||||
{
|
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
|
||||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
|
||||||
this.LogException(e, "Unhandled exception on connect");
|
|
||||||
DisconnectLogged = true;
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
|
||||||
if (AutoReconnect)
|
|
||||||
{
|
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
|
||||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnect the clients and put away it's resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
ConnectEnabled = false;
|
|
||||||
// Stop trying reconnects, if we are
|
|
||||||
if (ReconnectTimer != null)
|
|
||||||
{
|
|
||||||
ReconnectTimer.Stop();
|
|
||||||
// ReconnectTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kills the stream, cleans up the client and sets it to null
|
|
||||||
/// </summary>
|
|
||||||
private void KillClient(SocketStatus status)
|
|
||||||
{
|
|
||||||
KillStream();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Client.ErrorOccurred -= Client_ErrorOccurred;
|
|
||||||
Client.Disconnect();
|
|
||||||
Client.Dispose();
|
|
||||||
Client = null;
|
|
||||||
ClientStatus = status;
|
|
||||||
this.LogDebug("Disconnected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.LogException(ex,"Exception in Kill Client");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kills the stream
|
|
||||||
/// </summary>
|
|
||||||
void KillStream()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (TheStream != null)
|
|
||||||
{
|
|
||||||
TheStream.DataReceived -= Stream_DataReceived;
|
|
||||||
TheStream.Close();
|
|
||||||
TheStream.Dispose();
|
|
||||||
TheStream = null;
|
|
||||||
this.LogDebug("Disconnected stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.LogException(ex, "Exception in Kill Stream:{0}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the keyboard interactive authentication, should it be required.
|
|
||||||
/// </summary>
|
|
||||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
|
||||||
{
|
|
||||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
|
||||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
|
||||||
prompt.Response = Password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
|
||||||
/// </summary>
|
|
||||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
|
||||||
{
|
|
||||||
if (((ShellStream)sender).Length <= 0L)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var response = ((ShellStream)sender).Read();
|
|
||||||
|
|
||||||
var bytesHandler = BytesReceived;
|
|
||||||
|
|
||||||
if (bytesHandler != null)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(response);
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
{
|
|
||||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
|
||||||
/// event
|
|
||||||
/// </summary>
|
|
||||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
|
||||||
{
|
|
||||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
|
||||||
this.LogError("Disconnected by remote");
|
|
||||||
else
|
|
||||||
this.LogException(e.Exception, "Unhandled SSH client error");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Wait();
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Release();
|
|
||||||
}
|
|
||||||
if (AutoReconnect && ConnectEnabled)
|
|
||||||
{
|
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
|
||||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper for ConnectionChange event
|
|
||||||
/// </summary>
|
|
||||||
void OnConnectionChange()
|
|
||||||
{
|
|
||||||
if (ConnectionChange != null)
|
|
||||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IBasicCommunication Members
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends text to the server
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Client != null && TheStream != null && IsConnected)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
|
||||||
this.LogInformation(
|
|
||||||
"Sending {length} characters of text: '{text}'",
|
|
||||||
text.Length,
|
|
||||||
ComTextHelper.GetDebugText(text));
|
|
||||||
|
|
||||||
TheStream.Write(text);
|
|
||||||
TheStream.Flush();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.LogDebug("Client is null or disconnected. Cannot Send Text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
|
||||||
ReconnectTimer.Reset();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.LogException(ex, "Exception sending text: '{message}'", text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends Bytes to the server
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Client != null && TheStream != null && IsConnected)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
|
||||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
|
|
||||||
TheStream.Write(bytes, 0, bytes.Length);
|
|
||||||
TheStream.Flush();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.LogDebug("Client is null or disconnected. Cannot Send Bytes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException ex)
|
|
||||||
{
|
|
||||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
|
||||||
ReconnectTimer.Reset();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************************************
|
|
||||||
//*****************************************************************************************************
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when connection changes
|
|
||||||
/// </summary>
|
|
||||||
public class SshConnectionChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Connection State
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connection Status represented as a ushort
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The client
|
|
||||||
/// </summary>
|
|
||||||
public GenericSshClient Client { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Socket Status as represented by
|
|
||||||
/// </summary>
|
|
||||||
public ushort Status { get { return Client.UStatus; } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public SshConnectionChangeEventArgs() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// EventArgs class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isConnected">Connection State</param>
|
|
||||||
/// <param name="client">The Client</param>
|
|
||||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
|
||||||
{
|
|
||||||
IsConnected = isConnected;
|
|
||||||
Client = client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,566 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class to handle basic TCP/IP communications with a server
|
|
||||||
/// </summary>
|
|
||||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
|
||||||
{
|
|
||||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
|
||||||
/// <summary>
|
|
||||||
/// Object to enable stream debugging
|
|
||||||
/// </summary>
|
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires when data is received from the server and returns it as a Byte array
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires when data is received from the server and returns it as text
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
|
|
||||||
private string _hostname;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_hostname = value;
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
_client.AddressClientConnectedTo = _hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The actual client class
|
|
||||||
/// </summary>
|
|
||||||
private TCPClient _client;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is connected
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// _client socket status Read only
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected would be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status text shows the message associated with socket status
|
|
||||||
/// </summary>
|
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort representation of client status
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete]
|
|
||||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connection failure reason
|
|
||||||
/// </summary>
|
|
||||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set only when the disconnect method is called
|
|
||||||
/// </summary>
|
|
||||||
bool DisconnectCalledByUser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool Connected
|
|
||||||
{
|
|
||||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
|
||||||
}
|
|
||||||
|
|
||||||
//Lock object to prevent simulatneous connect/disconnect operations
|
|
||||||
private CCriticalSection connectLock = new CCriticalSection();
|
|
||||||
|
|
||||||
// private Timer for auto reconnect
|
|
||||||
private CTimer RetryTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">unique string to differentiate between instances</param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bufferSize"></param>
|
|
||||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
Hostname = address;
|
|
||||||
Port = port;
|
|
||||||
BufferSize = bufferSize;
|
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
public GenericTcpIpClient(string key)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor for S+
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpIpClient()
|
|
||||||
: base(SplusKey)
|
|
||||||
{
|
|
||||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Just to help S+ set the key
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
|
||||||
Deactivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override bool Deactivate()
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer.Dispose();
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
|
||||||
DisconnectClient();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to connect to the server
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Port < 1 || Port > 65535)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Enter();
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Stop retry timer if running
|
|
||||||
RetryTimer.Stop();
|
|
||||||
_client = new TCPClient(Hostname, Port, BufferSize);
|
|
||||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
|
||||||
_client.SocketStatusChange += Client_SocketStatusChange;
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reconnect()
|
|
||||||
{
|
|
||||||
if (_client == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Enter();
|
|
||||||
if (IsConnected || DisconnectCalledByUser == true)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Attempting reconnect now");
|
|
||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to disconnect the client
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Enter();
|
|
||||||
DisconnectCalledByUser = true;
|
|
||||||
|
|
||||||
// Stop trying reconnects, if we are
|
|
||||||
RetryTimer.Stop();
|
|
||||||
DisconnectClient();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does the actual disconnect business
|
|
||||||
/// </summary>
|
|
||||||
public void DisconnectClient()
|
|
||||||
{
|
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Disconnecting client");
|
|
||||||
if (IsConnected)
|
|
||||||
_client.DisconnectFromServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback method for connection attempt
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="c"></param>
|
|
||||||
void ConnectToServerCallback(TCPClient c)
|
|
||||||
{
|
|
||||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
|
||||||
WaitAndTryReconnect();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnects, waits and attemtps to connect again
|
|
||||||
/// </summary>
|
|
||||||
void WaitAndTryReconnect()
|
|
||||||
{
|
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
connectLock.Enter();
|
|
||||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
|
||||||
{
|
|
||||||
DisconnectClient();
|
|
||||||
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
|
||||||
RetryTimer.Reset(AutoReconnectIntervalMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Recieves incoming data
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
void Receive(TCPClient client, int numBytes)
|
|
||||||
{
|
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
if (numBytes > 0)
|
|
||||||
{
|
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
|
||||||
var bytesHandler = BytesReceived;
|
|
||||||
if (bytesHandler != null)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
}
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
{
|
|
||||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
|
||||||
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.ReceiveDataAsync(Receive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
// Check debug level before processing byte array
|
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
|
||||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
|
||||||
if (_client != null)
|
|
||||||
_client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is useful from console and...?
|
|
||||||
/// </summary>
|
|
||||||
public void SendEscapedText(string text)
|
|
||||||
{
|
|
||||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
|
||||||
{
|
|
||||||
var hex = s.Groups[1].Value;
|
|
||||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
|
||||||
});
|
|
||||||
SendText(unescapedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends Bytes to the server
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
|
||||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
|
||||||
if (_client != null)
|
|
||||||
_client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Socket Status Change Handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="clientSocketStatus"></param>
|
|
||||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
|
||||||
{
|
|
||||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
|
||||||
WaitAndTryReconnect();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
|
||||||
_client.ReceiveDataAsync(Receive);
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = ConnectionChange;
|
|
||||||
if (handler != null)
|
|
||||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configuration properties for TCP/SSH Connections
|
|
||||||
/// </summary>
|
|
||||||
public class TcpSshPropertiesConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Address to connect to
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
|
||||||
public string Address { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port to connect to
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Username credential
|
|
||||||
/// </summary>
|
|
||||||
public string Username { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Passord credential
|
|
||||||
/// </summary>
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 32768
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to true
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 5000ms
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor
|
|
||||||
/// </summary>
|
|
||||||
public TcpSshPropertiesConfig()
|
|
||||||
{
|
|
||||||
BufferSize = 32768;
|
|
||||||
AutoReconnect = true;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
Username = "";
|
|
||||||
Password = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,775 +0,0 @@
|
|||||||
/*PepperDash Technology Corp.
|
|
||||||
JAG
|
|
||||||
Copyright: 2017
|
|
||||||
------------------------------------
|
|
||||||
***Notice of Ownership and Copyright***
|
|
||||||
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
|
||||||
which claims copyright under the laws of the United States of America in the entire body of material
|
|
||||||
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
|
||||||
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
|
||||||
PepperDash Technology Corporation reserves all rights under applicable laws.
|
|
||||||
------------------------------------ */
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic TCP/IP client for server
|
|
||||||
/// </summary>
|
|
||||||
public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Band aid delegate for choked server
|
|
||||||
/// </summary>
|
|
||||||
internal delegate void ConnectionHasHungCallbackDelegate();
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of text received
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of socket status change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is something of a band-aid callback. If the client times out during the connection process, because the server
|
|
||||||
/// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help
|
|
||||||
/// keep a watch on the server and reset it if necessary.
|
|
||||||
/// </summary>
|
|
||||||
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require
|
|
||||||
/// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties & Variables
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Address of server
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Port on server
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper
|
|
||||||
/// </summary>
|
|
||||||
public ushort UPort
|
|
||||||
{
|
|
||||||
get { return Convert.ToUInt16(Port); }
|
|
||||||
set { Port = Convert.ToInt32(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
|
||||||
/// </summary>
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for requires shared key bool
|
|
||||||
/// </summary>
|
|
||||||
public ushort USharedKeyRequired
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == 1)
|
|
||||||
SharedKeyRequired = true;
|
|
||||||
else
|
|
||||||
SharedKeyRequired = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// flag to show the client is waiting for the server to send the shared key
|
|
||||||
/// </summary>
|
|
||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defaults to 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Semaphore on connect method
|
|
||||||
/// </summary>
|
|
||||||
bool IsTryingToConnect;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is connected
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsConnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsConnected
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool showing if socket is ready for communication after shared key exchange
|
|
||||||
/// </summary>
|
|
||||||
public bool IsReadyForCommunication { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for IsReadyForCommunication
|
|
||||||
/// </summary>
|
|
||||||
public ushort UIsReadyForCommunication
|
|
||||||
{
|
|
||||||
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client socket status Read only
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
return Client.ClientStatus;
|
|
||||||
else
|
|
||||||
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
|
||||||
/// and IsConnected would be true when this == 2.
|
|
||||||
/// </summary>
|
|
||||||
public ushort UStatus
|
|
||||||
{
|
|
||||||
get { return (ushort)ClientStatus; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status text shows the message associated with socket status
|
|
||||||
/// </summary>
|
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoReconnect { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper for AutoReconnect
|
|
||||||
/// </summary>
|
|
||||||
public ushort UAutoReconnect
|
|
||||||
{
|
|
||||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
|
||||||
set { AutoReconnect = value == 1; }
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
|
||||||
/// </summary>
|
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag Set only when the disconnect method is called.
|
|
||||||
/// </summary>
|
|
||||||
bool DisconnectCalledByUser;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// private Timer for auto reconnect
|
|
||||||
/// </summary>
|
|
||||||
CTimer RetryTimer;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool HeartbeatEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort UHeartbeatEnabled
|
|
||||||
{
|
|
||||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
|
||||||
set { HeartbeatEnabled = value == 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string HeartbeatString = "heartbeat";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int HeartbeatInterval = 50000;
|
|
||||||
|
|
||||||
CTimer HeartbeatSendTimer;
|
|
||||||
CTimer HeartbeatAckTimer;
|
|
||||||
/// <summary>
|
|
||||||
/// Used to force disconnection on a dead connect attempt
|
|
||||||
/// </summary>
|
|
||||||
CTimer ConnectFailTimer;
|
|
||||||
CTimer WaitForSharedKey;
|
|
||||||
private int ConnectionCount;
|
|
||||||
/// <summary>
|
|
||||||
/// Internal secure client
|
|
||||||
/// </summary>
|
|
||||||
TCPClient Client;
|
|
||||||
|
|
||||||
bool ProgramIsStopping;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bufferSize"></param>
|
|
||||||
public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize)
|
|
||||||
: base(key)
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
Hostname = address;
|
|
||||||
Port = port;
|
|
||||||
BufferSize = bufferSize;
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for S+
|
|
||||||
/// </summary>
|
|
||||||
public GenericTcpIpClient_ForServer()
|
|
||||||
: base("Uninitialized DynamicTcpClient")
|
|
||||||
{
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
|
||||||
AutoReconnectIntervalMs = 5000;
|
|
||||||
BufferSize = 2000;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Just to help S+ set the key
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles closing this up when the program shuts down
|
|
||||||
/// </summary>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
|
||||||
ProgramIsStopping = true;
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
ConnectionCount++;
|
|
||||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsTryingToConnect = true;
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Port < 1 || Port > 65535)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up previous client
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
DisconnectCalledByUser = false;
|
|
||||||
|
|
||||||
Client = new TCPClient(Hostname, Port, BufferSize);
|
|
||||||
Client.SocketStatusChange += Client_SocketStatusChange;
|
|
||||||
if(HeartbeatEnabled)
|
|
||||||
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
|
|
||||||
Client.AddressClientConnectedTo = Hostname;
|
|
||||||
Client.PortNumber = Port;
|
|
||||||
// SecureClient = c;
|
|
||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
|
||||||
|
|
||||||
ConnectFailTimer = new CTimer(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
|
||||||
if (IsTryingToConnect)
|
|
||||||
{
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
//if (ConnectionHasHungCallback != null)
|
|
||||||
//{
|
|
||||||
// ConnectionHasHungCallback();
|
|
||||||
//}
|
|
||||||
//SecureClient.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
|
||||||
Client.ConnectToServerAsync(o =>
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
|
||||||
{
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
}
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
|
||||||
o.ReceiveDataAsync(Receive);
|
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
|
||||||
{
|
|
||||||
WaitingForSharedKeyResponse = true;
|
|
||||||
WaitForSharedKey = new CTimer(timer =>
|
|
||||||
{
|
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
|
||||||
o.DisconnectFromServer();
|
|
||||||
//CheckClosedAndTryReconnect();
|
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//CLient connected and shared key is not required so just raise the ready for communication event. if Shared key
|
|
||||||
//required this is called by the shared key being negotiated
|
|
||||||
if (IsReadyForCommunication == false)
|
|
||||||
{
|
|
||||||
OnClientReadyForcommunications(true); // Key not required
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void Disconnect()
|
|
||||||
{
|
|
||||||
this.LogVerbose("Disconnect Called");
|
|
||||||
|
|
||||||
DisconnectCalledByUser = true;
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Client.DisconnectFromServer();
|
|
||||||
|
|
||||||
}
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal call to close up client. ALWAYS use this when disconnecting.
|
|
||||||
/// </summary>
|
|
||||||
void Cleanup()
|
|
||||||
{
|
|
||||||
IsTryingToConnect = false;
|
|
||||||
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
//SecureClient.DisconnectFromServer();
|
|
||||||
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
|
||||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
|
||||||
Client.Dispose();
|
|
||||||
Client = null;
|
|
||||||
}
|
|
||||||
if (ConnectFailTimer != null)
|
|
||||||
{
|
|
||||||
ConnectFailTimer.Stop();
|
|
||||||
ConnectFailTimer.Dispose();
|
|
||||||
ConnectFailTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>ff
|
|
||||||
/// Called from Connect failure or Socket Status change if
|
|
||||||
/// auto reconnect and socket disconnected (Not disconnected by user)
|
|
||||||
/// </summary>
|
|
||||||
void CheckClosedAndTryReconnect()
|
|
||||||
{
|
|
||||||
if (Client != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
|
||||||
{
|
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
|
||||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
|
||||||
if (RetryTimer != null)
|
|
||||||
{
|
|
||||||
RetryTimer.Stop();
|
|
||||||
RetryTimer = null;
|
|
||||||
}
|
|
||||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="numBytes"></param>
|
|
||||||
void Receive(TCPClient client, int numBytes)
|
|
||||||
{
|
|
||||||
if (numBytes > 0)
|
|
||||||
{
|
|
||||||
string str = string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
|
||||||
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
|
||||||
{
|
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
|
||||||
SendText(SharedKey + "\n");
|
|
||||||
}
|
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
|
||||||
{
|
|
||||||
StopWaitForSharedKeyTimer();
|
|
||||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//var bytesHandler = BytesReceived;
|
|
||||||
//if (bytesHandler != null)
|
|
||||||
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
client.ReceiveDataAsync(Receive);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HeartbeatStart()
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Starting Heartbeat");
|
|
||||||
if (HeartbeatSendTimer == null)
|
|
||||||
{
|
|
||||||
|
|
||||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer == null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void HeartbeatStop()
|
|
||||||
{
|
|
||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
|
||||||
HeartbeatSendTimer.Stop();
|
|
||||||
HeartbeatSendTimer = null;
|
|
||||||
}
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
|
||||||
HeartbeatAckTimer.Stop();
|
|
||||||
HeartbeatAckTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void SendHeartbeat(object notused)
|
|
||||||
{
|
|
||||||
this.SendText(HeartbeatString);
|
|
||||||
Debug.Console(2, this, "Sending Heartbeat");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//private method to check heartbeat requirements and start or reset timer
|
|
||||||
string checkHeartbeat(string received)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (HeartbeatEnabled)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(HeartbeatString))
|
|
||||||
{
|
|
||||||
var remainingText = received.Replace(HeartbeatString, "");
|
|
||||||
var noDelimiter = received.Trim(new char[] { '\r', '\n' });
|
|
||||||
if (noDelimiter.Contains(HeartbeatString))
|
|
||||||
{
|
|
||||||
if (HeartbeatAckTimer != null)
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
|
||||||
}
|
|
||||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
|
||||||
return remainingText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void HeartbeatAckTimerFail(object o)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
void StopWaitForSharedKeyTimer()
|
|
||||||
{
|
|
||||||
if (WaitForSharedKey != null)
|
|
||||||
{
|
|
||||||
WaitForSharedKey.Stop();
|
|
||||||
WaitForSharedKey = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// General send method
|
|
||||||
/// </summary>
|
|
||||||
public void SendText(string text)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
|
||||||
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
Client.SendDataAsync(bytes, bytes.Length, (c, n) =>
|
|
||||||
{
|
|
||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
|
||||||
if (n <= 0)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public void SendBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
if (bytes.Length > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
Client.SendData(bytes, bytes.Length);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SocketStatusChange Callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="clientSocketStatus"></param>
|
|
||||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
|
||||||
{
|
|
||||||
if (ProgramIsStopping)
|
|
||||||
{
|
|
||||||
ProgramIsStopping = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
|
||||||
|
|
||||||
OnConnectionChange();
|
|
||||||
|
|
||||||
// The client could be null or disposed by this time...
|
|
||||||
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
|
||||||
{
|
|
||||||
HeartbeatStop();
|
|
||||||
OnClientReadyForcommunications(false); // socket has gone low
|
|
||||||
CheckClosedAndTryReconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper for ConnectionChange event
|
|
||||||
/// </summary>
|
|
||||||
void OnConnectionChange()
|
|
||||||
{
|
|
||||||
var handler = ConnectionChange;
|
|
||||||
if (handler != null)
|
|
||||||
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to fire ClientReadyForCommunications event
|
|
||||||
/// </summary>
|
|
||||||
void OnClientReadyForcommunications(bool isReady)
|
|
||||||
{
|
|
||||||
IsReadyForCommunication = isReady;
|
|
||||||
if (this.IsReadyForCommunication) { HeartbeatStart(); }
|
|
||||||
var handler = ClientReadyForCommunications;
|
|
||||||
if (handler != null)
|
|
||||||
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,396 +0,0 @@
|
|||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic UDP Server device
|
|
||||||
/// </summary>
|
|
||||||
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|
||||||
{
|
|
||||||
private const string SplusKey = "Uninitialized Udp Server";
|
|
||||||
/// <summary>
|
|
||||||
/// Object to enable stream debugging
|
|
||||||
/// </summary>
|
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
|
||||||
|
|
||||||
/// <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.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SocketStatus ClientStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
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="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;
|
|
||||||
|
|
||||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
|
||||||
Disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enables the UDP Server
|
|
||||||
/// </summary>
|
|
||||||
public void Connect()
|
|
||||||
{
|
|
||||||
if (Server == null)
|
|
||||||
{
|
|
||||||
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>
|
|
||||||
/// 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)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
|
||||||
}
|
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
|
||||||
}
|
|
||||||
var textHandler = TextReceived;
|
|
||||||
if (textHandler != null)
|
|
||||||
{
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
|
||||||
Debug.Console(0, this, "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)
|
|
||||||
Debug.Console(0, this, "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)
|
|
||||||
Debug.Console(0, this, "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; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string IpAddress { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Bytes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <param name="ipAddress"></param>
|
|
||||||
/// <param name="port"></param>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
|
||||||
{
|
|
||||||
Text = text;
|
|
||||||
IpAddress = ipAddress;
|
|
||||||
Port = port;
|
|
||||||
Bytes = bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stupid S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericUdpReceiveTextExtraArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class UdpServerPropertiesConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// TcpSsh Properties
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("control")]
|
|
||||||
public ControlPropertiesConfig Control { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("secure")]
|
|
||||||
public bool Secure { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sharedKeyRequired")]
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The shared key that must match on the server and client
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sharedKey")]
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("heartbeatRequired")]
|
|
||||||
public bool HeartbeatRequired { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("heartbeatStringToMatch")]
|
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("receiveQueueSize")]
|
|
||||||
public int ReceiveQueueSize { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// Uique key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Max Clients that the server will allow to connect.
|
|
||||||
/// </summary>
|
|
||||||
public ushort MaxClients { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
|
||||||
/// </summary>
|
|
||||||
public bool Secure { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Port for the server to listen on
|
|
||||||
/// </summary>
|
|
||||||
public int Port { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
|
||||||
/// </summary>
|
|
||||||
public bool SharedKeyRequired { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The shared key that must match on the server and client
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public bool HeartbeatRequired { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
|
||||||
/// </summary>
|
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
|
||||||
/// </summary>
|
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
|
||||||
/// </summary>
|
|
||||||
public int BufferSize { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
|
||||||
/// </summary>
|
|
||||||
public int ReceiveQueueSize { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Crestron Control Methods for a comm object
|
|
||||||
/// </summary>
|
|
||||||
public enum eControlMethod
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// RS232/422/485
|
|
||||||
/// </summary>
|
|
||||||
Com,
|
|
||||||
/// <summary>
|
|
||||||
/// Crestron IpId (most Crestron ethernet devices)
|
|
||||||
/// </summary>
|
|
||||||
IpId,
|
|
||||||
/// <summary>
|
|
||||||
/// Crestron IpIdTcp (HD-MD series, etc.)
|
|
||||||
/// </summary>
|
|
||||||
IpidTcp,
|
|
||||||
/// <summary>
|
|
||||||
/// Crestron IR control
|
|
||||||
/// </summary>
|
|
||||||
IR,
|
|
||||||
/// <summary>
|
|
||||||
/// SSH client
|
|
||||||
/// </summary>
|
|
||||||
Ssh,
|
|
||||||
/// <summary>
|
|
||||||
/// TCP/IP client
|
|
||||||
/// </summary>
|
|
||||||
Tcpip,
|
|
||||||
/// <summary>
|
|
||||||
/// Telnet
|
|
||||||
/// </summary>
|
|
||||||
Telnet,
|
|
||||||
/// <summary>
|
|
||||||
/// Crestnet device
|
|
||||||
/// </summary>
|
|
||||||
Cresnet,
|
|
||||||
/// <summary>
|
|
||||||
/// CEC Control, via a DM HDMI port
|
|
||||||
/// </summary>
|
|
||||||
Cec,
|
|
||||||
/// <summary>
|
|
||||||
/// UDP Server
|
|
||||||
/// </summary>
|
|
||||||
Udp,
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP client
|
|
||||||
/// </summary>
|
|
||||||
Http,
|
|
||||||
/// <summary>
|
|
||||||
/// HTTPS client
|
|
||||||
/// </summary>
|
|
||||||
Https,
|
|
||||||
/// <summary>
|
|
||||||
/// Websocket client
|
|
||||||
/// </summary>
|
|
||||||
Ws,
|
|
||||||
/// <summary>
|
|
||||||
/// Secure Websocket client
|
|
||||||
/// </summary>
|
|
||||||
Wss,
|
|
||||||
/// <summary>
|
|
||||||
/// Secure TCP/IP
|
|
||||||
/// </summary>
|
|
||||||
SecureTcpIp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An incoming communication stream
|
|
||||||
/// </summary>
|
|
||||||
public interface ICommunicationReceiver : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of bytes received
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
/// Represents a device that uses basic connection
|
|
||||||
/// </summary>
|
|
||||||
public interface IBasicCommunication : ICommunicationReceiver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Send text to the device
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
void SendText(string text);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send bytes to the device
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
void SendBytes(byte[] bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
|
||||||
/// </summary>
|
|
||||||
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a device with stream debugging capablities
|
|
||||||
/// </summary>
|
|
||||||
public interface IStreamDebugging
|
|
||||||
{
|
|
||||||
/// <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>
|
|
||||||
/// Notifies of socket status changes
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
/// 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>
|
|
||||||
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>
|
|
||||||
public enum eGenericCommMethodStatusChangeType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Connected
|
|
||||||
/// </summary>
|
|
||||||
Connected,
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnected
|
|
||||||
/// </summary>
|
|
||||||
Disconnected
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This delegate defines handler for IBasicCommunication status changes
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="comm">Device firing the status change</param>
|
|
||||||
/// <param name="status"></param>
|
|
||||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Bytes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
|
||||||
{
|
|
||||||
Bytes = bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericCommMethodReceiveBytesArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Delimiter { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
public GenericCommMethodReceiveTextArgs(string text)
|
|
||||||
{
|
|
||||||
Text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
/// <param name="delimiter"></param>
|
|
||||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
|
||||||
:this(text)
|
|
||||||
{
|
|
||||||
Delimiter = delimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ Constructor
|
|
||||||
/// </summary>
|
|
||||||
public GenericCommMethodReceiveTextArgs() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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,235 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Config
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reads a Portal formatted config file
|
|
||||||
/// </summary>
|
|
||||||
public class PortalConfigReader
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>JObject of config file</returns>
|
|
||||||
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Error,
|
|
||||||
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (StreamReader fs = new StreamReader(filePath))
|
|
||||||
{
|
|
||||||
var jsonObj = JObject.Parse(fs.ReadToEnd());
|
|
||||||
if(jsonObj["template"] != null && jsonObj["system"] != null)
|
|
||||||
{
|
|
||||||
// it's a double-config, merge it.
|
|
||||||
var merged = MergeConfigs(jsonObj);
|
|
||||||
if (jsonObj["system_url"] != null)
|
|
||||||
{
|
|
||||||
merged["systemUrl"] = jsonObj["system_url"].Value<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonObj["template_url"] != null)
|
|
||||||
{
|
|
||||||
merged["templateUrl"] = jsonObj["template_url"].Value<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonObj = merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (StreamWriter fw = new StreamWriter(savePath))
|
|
||||||
{
|
|
||||||
fw.Write(jsonObj.ToString(Formatting.Indented));
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "JSON config merged and saved to {0}", savePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "ERROR: Config load failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="doubleConfig"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static JObject MergeConfigs(JObject doubleConfig)
|
|
||||||
{
|
|
||||||
var system = JObject.FromObject(doubleConfig["system"]);
|
|
||||||
var template = JObject.FromObject(doubleConfig["template"]);
|
|
||||||
var merged = new JObject();
|
|
||||||
|
|
||||||
// Put together top-level objects
|
|
||||||
if (system["info"] != null)
|
|
||||||
merged.Add("info", Merge(template["info"], system["info"], "infO"));
|
|
||||||
else
|
|
||||||
merged.Add("info", template["info"]);
|
|
||||||
|
|
||||||
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
|
|
||||||
system["devices"] as JArray, "key", "devices"));
|
|
||||||
|
|
||||||
if (system["rooms"] == null)
|
|
||||||
merged.Add("rooms", template["rooms"]);
|
|
||||||
else
|
|
||||||
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
|
|
||||||
system["rooms"] as JArray, "key", "rooms"));
|
|
||||||
|
|
||||||
if (system["sourceLists"] == null)
|
|
||||||
merged.Add("sourceLists", template["sourceLists"]);
|
|
||||||
else
|
|
||||||
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
|
|
||||||
|
|
||||||
if (system["destinationLists"] == null)
|
|
||||||
merged.Add("destinationLists", template["destinationLists"]);
|
|
||||||
else
|
|
||||||
merged.Add("destinationLists",
|
|
||||||
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
|
|
||||||
|
|
||||||
|
|
||||||
if (system["cameraLists"] == null)
|
|
||||||
merged.Add("cameraLists", template["cameraLists"]);
|
|
||||||
else
|
|
||||||
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
|
|
||||||
|
|
||||||
if (system["audioControlPointLists"] == null)
|
|
||||||
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
|
|
||||||
else
|
|
||||||
merged.Add("audioControlPointLists",
|
|
||||||
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
|
|
||||||
|
|
||||||
|
|
||||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
|
||||||
// level anyway...
|
|
||||||
if (template["tieLines"] != null)
|
|
||||||
merged.Add("tieLines", template["tieLines"]);
|
|
||||||
else if (system["tieLines"] != null)
|
|
||||||
merged.Add("tieLines", system["tieLines"]);
|
|
||||||
else
|
|
||||||
merged.Add("tieLines", new JArray());
|
|
||||||
|
|
||||||
if (template["joinMaps"] != null)
|
|
||||||
merged.Add("joinMaps", template["joinMaps"]);
|
|
||||||
else
|
|
||||||
merged.Add("joinMaps", new JObject());
|
|
||||||
|
|
||||||
if (system["global"] != null)
|
|
||||||
merged.Add("global", Merge(template["global"], system["global"], "global"));
|
|
||||||
else
|
|
||||||
merged.Add("global", template["global"]);
|
|
||||||
|
|
||||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Merges the contents of a base and a delta array, matching the entries on a top-level property
|
|
||||||
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have
|
|
||||||
/// a matched item in base array will not be merged. Non keyed system items will replace the template items.
|
|
||||||
/// </summary>
|
|
||||||
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path)
|
|
||||||
{
|
|
||||||
var result = new JArray();
|
|
||||||
if (a2 == null || a2.Count == 0) // If the system array is null or empty, return the template array
|
|
||||||
return a1;
|
|
||||||
else if (a1 != null)
|
|
||||||
{
|
|
||||||
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
|
|
||||||
{ // with the system array
|
|
||||||
return a2;
|
|
||||||
}
|
|
||||||
else // The arrays are keyed, merge them by key
|
|
||||||
{
|
|
||||||
for (int i = 0; i < a1.Count(); i++)
|
|
||||||
{
|
|
||||||
var a1Dev = a1[i];
|
|
||||||
// Try to get a system device and if found, merge it onto template
|
|
||||||
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper for using with JTokens. Converts to JObject
|
|
||||||
/// </summary>
|
|
||||||
static JObject Merge(JToken t1, JToken t2, string path)
|
|
||||||
{
|
|
||||||
return Merge(JObject.FromObject(t1), JObject.FromObject(t2), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Merge o2 onto o1
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o1"></param>
|
|
||||||
/// <param name="o2"></param>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
static JObject Merge(JObject o1, JObject o2, string path)
|
|
||||||
{
|
|
||||||
foreach (var o2Prop in o2)
|
|
||||||
{
|
|
||||||
var propKey = o2Prop.Key;
|
|
||||||
var o1Value = o1[propKey];
|
|
||||||
var o2Value = o2[propKey];
|
|
||||||
|
|
||||||
// if the property doesn't exist on o1, then add it.
|
|
||||||
if (o1Value == null)
|
|
||||||
{
|
|
||||||
o1.Add(propKey, o2Value);
|
|
||||||
}
|
|
||||||
// otherwise merge them
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Drill down
|
|
||||||
var propPath = String.Format("{0}.{1}", path, propKey);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
if (o1Value is JArray)
|
|
||||||
{
|
|
||||||
if (o2Value is JArray)
|
|
||||||
{
|
|
||||||
o1Value.Replace(MergeArraysOnTopLevelProperty(o1Value as JArray, o2Value as JArray, "key", propPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (o2Prop.Value.HasValues && o1Value.HasValues)
|
|
||||||
{
|
|
||||||
o1Value.Replace(Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Value), propPath));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o1Value.Replace(o2Prop.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
public class EncodingHelper
|
|
||||||
{
|
|
||||||
public static string ConvertUtf8ToAscii(string utf8String)
|
|
||||||
{
|
|
||||||
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ConvertUtf8ToUtf16(string utf8String)
|
|
||||||
{
|
|
||||||
return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unique key interface to require a unique key for the class
|
|
||||||
/// </summary>
|
|
||||||
public interface IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unique Key
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("key")]
|
|
||||||
string Key { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Named Keyed device interface. Forces the device to have a Unique Key and a name.
|
|
||||||
/// </summary>
|
|
||||||
public interface IKeyName : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Isn't it obvious :)
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
|
||||||
string Name { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
//*********************************************************************************************************
|
|
||||||
/// <summary>
|
|
||||||
/// The core event and status-bearing class that most if not all device and connectors can derive from.
|
|
||||||
/// </summary>
|
|
||||||
public class Device : IKeyName
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unique Key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; protected set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the devie
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; protected set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool Enabled { get; protected set; }
|
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
///// A place to store reference to the original config object, if any. These values should
|
|
||||||
///// NOT be used as properties on the device as they are all publicly-settable values.
|
|
||||||
///// </summary>
|
|
||||||
//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;
|
|
||||||
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>
|
|
||||||
/// 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>
|
|
||||||
public void AddPostActivationAction(Action act)
|
|
||||||
{
|
|
||||||
if (_PostActivationActions == null)
|
|
||||||
_PostActivationActions = new List<Action>();
|
|
||||||
_PostActivationActions.Add(act);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Executes the preactivation actions
|
|
||||||
/// </summary>
|
|
||||||
public void PreActivate()
|
|
||||||
{
|
|
||||||
if (_PreActivationActions != null)
|
|
||||||
_PreActivationActions.ForEach(a => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
a.Invoke();
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
|
|
||||||
/// all post-activation at end. Classes needing additional logic to
|
|
||||||
/// run should override CustomActivate()
|
|
||||||
/// </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>
|
|
||||||
/// Executes the postactivation actions
|
|
||||||
/// </summary>
|
|
||||||
public void PostActivate()
|
|
||||||
{
|
|
||||||
if (_PostActivationActions != null)
|
|
||||||
_PostActivationActions.ForEach(a => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
a.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called in between Pre and PostActivationActions when Activate() is called.
|
|
||||||
/// Override to provide addtitional setup when calling activation. Overriding classes
|
|
||||||
/// do not need to call base.CustomActivate()
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>true if device activated successfully.</returns>
|
|
||||||
public virtual bool CustomActivate() { return true; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call to deactivate device - unlink events, etc. Overriding classes do not
|
|
||||||
/// need to call base.Deactivate()
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public virtual bool Deactivate() { return true; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Initialize()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper method to check object for bool value false and fire an Action method
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="o">Should be of type bool, others will be ignored</param>
|
|
||||||
/// <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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="$(TargetDir)\$(TargetName).$(Version).cpz" Condition="$(ProjectType) == 'Program'">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>content;</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Include="$(PackageOutputPath)\$(TargetName).$(Version).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>content;</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
<Target Name="Create CPLZ" AfterTargets="Build; Rebuild" Condition="$(ProjectType) == 'ProgramLibrary'">
|
|
||||||
<Message Text="Creating CPLZ"></Message>
|
|
||||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))"></MakeDir>
|
|
||||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).cplz" Overwrite="true"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Clean CPLZ" AfterTargets="AfterClean" Condition="$(ProjectType) == 'ProgramLibrary'">
|
|
||||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).cplz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CPZ" AfterTargets="SimplSharpPostProcess" Condition="$(ProjectType) == 'Program'">
|
|
||||||
<Message Text="Copying CPZ"></Message>
|
|
||||||
<Move SourceFiles="$(TargetDir)\$(TargetName).cpz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).cpz"/>
|
|
||||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).cpz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).cpz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Clean CPZ" AfterTargets="AfterClean" Condition="$(ProjectType) == 'Program'">
|
|
||||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).cpz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CLZ" AfterTargets="SimplSharpPostProcess">
|
|
||||||
<Message Text="Copying CLZ"></Message>
|
|
||||||
<Move SourceFiles="$(TargetDir)\$(TargetName).clz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).clz"/>
|
|
||||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).clz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).clz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="Clean CLZ" AfterTargets="AfterClean">
|
|
||||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).clz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="SimplSharpNewtonsoft" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
|
|
||||||
<ItemGroup>
|
|
||||||
<ReferencePath Condition="'%(FileName)' == 'Newtonsoft.Json.Compact'">
|
|
||||||
<Aliases>doNotUse</Aliases>
|
|
||||||
</ReferencePath>
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
name: Build Essentials Plugin
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
getVersion:
|
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
|
||||||
secrets: inherit
|
|
||||||
build-4Series:
|
|
||||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
|
||||||
secrets: inherit
|
|
||||||
needs: getVersion
|
|
||||||
if: needs.getVersion.outputs.newVersion == 'true'
|
|
||||||
with:
|
|
||||||
newVersion: ${{ needs.getVersion.outputs.newVersion }}
|
|
||||||
version: ${{ needs.getVersion.outputs.version }}
|
|
||||||
tag: ${{ needs.getVersion.outputs.tag }}
|
|
||||||
channel: ${{ needs.getVersion.outputs.channel }}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class to help with accessing values from the CrestronEthernetHelper class
|
|
||||||
/// </summary>
|
|
||||||
public class EthernetHelper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public static EthernetHelper LanHelper
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_LanHelper == null) _LanHelper = new EthernetHelper(0);
|
|
||||||
return _LanHelper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static EthernetHelper _LanHelper;
|
|
||||||
|
|
||||||
// ADD OTHER HELPERS HERE
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int PortNumber { get; private set; }
|
|
||||||
|
|
||||||
private EthernetHelper(int portNumber)
|
|
||||||
{
|
|
||||||
PortNumber = portNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("linkActive")]
|
|
||||||
public bool LinkActive
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var status = CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS, 0);
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "LinkActive = {0}", status);
|
|
||||||
return status == "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("dchpActive")]
|
|
||||||
public bool DhcpActive
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, 0) == "ON";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("hostname")]
|
|
||||||
public string Hostname
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("ipAddress")]
|
|
||||||
public string IPAddress
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("subnetMask")]
|
|
||||||
public string SubnetMask
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("defaultGateway")]
|
|
||||||
public string DefaultGateway
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return CrestronEthernetHelper.GetEthernetParameter(
|
|
||||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Bool change event args
|
|
||||||
/// </summary>
|
|
||||||
public class BoolChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Boolean state property
|
|
||||||
/// </summary>
|
|
||||||
public bool State { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Boolean ushort value property
|
|
||||||
/// </summary>
|
|
||||||
public ushort IntValue { get { return (ushort)(State ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Boolean change event args type
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Boolean change event args index
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public BoolChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public BoolChangeEventArgs(bool state, ushort type)
|
|
||||||
{
|
|
||||||
State = state;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public BoolChangeEventArgs(bool state, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
State = state;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort change event args
|
|
||||||
/// </summary>
|
|
||||||
public class UshrtChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort change event args integer value
|
|
||||||
/// </summary>
|
|
||||||
public ushort IntValue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort change event args type
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort change event args index
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public UshrtChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="intValue"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public UshrtChangeEventArgs(ushort intValue, ushort type)
|
|
||||||
{
|
|
||||||
IntValue = intValue;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="intValue"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public UshrtChangeEventArgs(ushort intValue, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
IntValue = intValue;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String change event args
|
|
||||||
/// </summary>
|
|
||||||
public class StringChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// String change event args value
|
|
||||||
/// </summary>
|
|
||||||
public string StringValue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String change event args type
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// string change event args index
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public StringChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stringValue"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public StringChangeEventArgs(string stringValue, ushort type)
|
|
||||||
{
|
|
||||||
StringValue = stringValue;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stringValue"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public StringChangeEventArgs(string stringValue, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
StringValue = stringValue;
|
|
||||||
Type = type;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants for simpl modules
|
|
||||||
/// </summary>
|
|
||||||
public class JsonStandardDeviceConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Json object evaluated constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort JsonObjectEvaluated = 2;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Json object changed constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort JsonObjectChanged = 104;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class DeviceChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Device change event args object
|
|
||||||
/// </summary>
|
|
||||||
public DeviceConfig Device { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device change event args type
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device change event args index
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor
|
|
||||||
/// </summary>
|
|
||||||
public DeviceChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="device"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public DeviceChangeEventArgs(DeviceConfig device, ushort type)
|
|
||||||
{
|
|
||||||
Device = device;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="device"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public DeviceChangeEventArgs(DeviceConfig device, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
Device = device;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using PepperDash.Core.JsonToSimpl;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Device class
|
|
||||||
/// </summary>
|
|
||||||
public class DeviceConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// JSON config key property
|
|
||||||
/// </summary>
|
|
||||||
public string key { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// JSON config name property
|
|
||||||
/// </summary>
|
|
||||||
public string name { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// JSON config type property
|
|
||||||
/// </summary>
|
|
||||||
public string type { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// JSON config properties
|
|
||||||
/// </summary>
|
|
||||||
public PropertiesConfig properties { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bool change event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort change event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
|
||||||
/// <summary>
|
|
||||||
/// String change event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Object change event handler
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<DeviceChangeEventArgs> DeviceChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public DeviceConfig()
|
|
||||||
{
|
|
||||||
properties = new PropertiesConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize method
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uniqueID"></param>
|
|
||||||
/// <param name="deviceKey"></param>
|
|
||||||
public void Initialize(string uniqueID, string deviceKey)
|
|
||||||
{
|
|
||||||
// S+ set EvaluateFb low
|
|
||||||
OnBoolChange(false, 0, JsonStandardDeviceConstants.JsonObjectEvaluated);
|
|
||||||
// validate parameters
|
|
||||||
if (string.IsNullOrEmpty(uniqueID) || string.IsNullOrEmpty(deviceKey))
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "UniqueID ({0} or key ({1} is null or empty", uniqueID, deviceKey);
|
|
||||||
// S+ set EvaluteFb high
|
|
||||||
OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
key = deviceKey;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// get the file using the unique ID
|
|
||||||
JsonToSimplMaster jsonMaster = J2SGlobal.GetMasterByFile(uniqueID);
|
|
||||||
if (jsonMaster == null)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Could not find JSON file with uniqueID {0}", uniqueID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the device configuration using the key
|
|
||||||
var devices = jsonMaster.JsonObject.ToObject<RootObject>().devices;
|
|
||||||
var device = devices.FirstOrDefault(d => d.key.Equals(key));
|
|
||||||
if (device == null)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Could not find device with key {0}", key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OnObjectChange(device, 0, JsonStandardDeviceConstants.JsonObjectChanged);
|
|
||||||
|
|
||||||
var index = devices.IndexOf(device);
|
|
||||||
OnStringChange(string.Format("devices[{0}]", index), 0, JsonToSimplConstants.FullPathToArrayChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("Device {0} lookup failed:\r{1}", key, e);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// S+ set EvaluteFb high
|
|
||||||
OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region EventHandler Helpers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BoolChange event handler helper
|
|
||||||
/// </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>
|
|
||||||
/// UshrtChange event handler helper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnUshrtChange(ushort state, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = UshrtChange;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new UshrtChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
UshrtChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// StringChange event handler helper
|
|
||||||
/// </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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ObjectChange event handler helper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="device"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnObjectChange(DeviceConfig device, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
if (DeviceChange != null)
|
|
||||||
{
|
|
||||||
var args = new DeviceChangeEventArgs(device, type);
|
|
||||||
args.Index = index;
|
|
||||||
DeviceChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion EventHandler Helpers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
Convert JSON snippt to C#: http://json2csharp.com/#
|
|
||||||
|
|
||||||
JSON Snippet:
|
|
||||||
{
|
|
||||||
"devices": [
|
|
||||||
{
|
|
||||||
"key": "deviceKey",
|
|
||||||
"name": "deviceName",
|
|
||||||
"type": "deviceType",
|
|
||||||
"properties": {
|
|
||||||
"deviceId": 1,
|
|
||||||
"enabled": true,
|
|
||||||
"control": {
|
|
||||||
"method": "methodName",
|
|
||||||
"controlPortDevKey": "deviceControlPortDevKey",
|
|
||||||
"controlPortNumber": 1,
|
|
||||||
"comParams": {
|
|
||||||
"baudRate": 9600,
|
|
||||||
"dataBits": 8,
|
|
||||||
"stopBits": 1,
|
|
||||||
"parity": "None",
|
|
||||||
"protocol": "RS232",
|
|
||||||
"hardwareHandshake": "None",
|
|
||||||
"softwareHandshake": "None",
|
|
||||||
"pacing": 0
|
|
||||||
},
|
|
||||||
"tcpSshProperties": {
|
|
||||||
"address": "172.22.1.101",
|
|
||||||
"port": 23,
|
|
||||||
"username": "user01",
|
|
||||||
"password": "password01",
|
|
||||||
"autoReconnect": false,
|
|
||||||
"autoReconnectIntervalMs": 10000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/// <summary>
|
|
||||||
/// Device communication parameter class
|
|
||||||
/// </summary>
|
|
||||||
public class ComParamsConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int baudRate { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int dataBits { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int stopBits { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string parity { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string protocol { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string hardwareHandshake { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string softwareHandshake { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int pacing { get; set; }
|
|
||||||
|
|
||||||
// convert properties for simpl
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplPacing { get { return Convert.ToUInt16(pacing); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ComParamsConfig()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device TCP/SSH properties class
|
|
||||||
/// </summary>
|
|
||||||
public class TcpSshPropertiesConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string address { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int port { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string username { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string password { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool autoReconnect { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int autoReconnectIntervalMs { get; set; }
|
|
||||||
|
|
||||||
// convert properties for simpl
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplPort { get { return Convert.ToUInt16(port); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public TcpSshPropertiesConfig()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device control class
|
|
||||||
/// </summary>
|
|
||||||
public class ControlConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string method { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string controlPortDevKey { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int controlPortNumber { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ComParamsConfig comParams { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public TcpSshPropertiesConfig tcpSshProperties { get; set; }
|
|
||||||
|
|
||||||
// convert properties for simpl
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ControlConfig()
|
|
||||||
{
|
|
||||||
comParams = new ComParamsConfig();
|
|
||||||
tcpSshProperties = new TcpSshPropertiesConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Device properties class
|
|
||||||
/// </summary>
|
|
||||||
public class PropertiesConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int deviceId { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public bool enabled { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ControlConfig control { get; set; }
|
|
||||||
|
|
||||||
// convert properties for simpl
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public PropertiesConfig()
|
|
||||||
{
|
|
||||||
control = new ControlConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Root device class
|
|
||||||
/// </summary>
|
|
||||||
public class RootObject
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The collection of devices
|
|
||||||
/// </summary>
|
|
||||||
public List<DeviceConfig> devices { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants for Simpl modules
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort BoolValueChange = 1;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort JsonIsValidBoolChange = 2;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the if the device is 3-series compatible
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProgramCompatibility3SeriesChange = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the if the device is 4-series compatible
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProgramCompatibility4SeriesChange = 4;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the device platform enum value
|
|
||||||
/// </summary>
|
|
||||||
public const ushort DevicePlatformValueChange = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort UshortValueChange = 101;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort StringValueChange = 201;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort FullPathToArrayChange = 202;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ActualFilePathChange = 203;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort FilenameResolvedChange = 204;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort FilePathResolvedChange = 205;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the root directory change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort RootDirectoryChange = 206;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the room ID change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort RoomIdChange = 207;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the room name change
|
|
||||||
/// </summary>
|
|
||||||
public const ushort RoomNameChange = 208;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ values delegate
|
|
||||||
/// </summary>
|
|
||||||
public delegate void SPlusValuesDelegate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ values wrapper
|
|
||||||
/// </summary>
|
|
||||||
public class SPlusValueWrapper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SPlusType ValueType { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort BoolUShortValue { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string StringValue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public SPlusValueWrapper() {}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public SPlusValueWrapper(SPlusType type, ushort index)
|
|
||||||
{
|
|
||||||
ValueType = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ types enum
|
|
||||||
/// </summary>
|
|
||||||
public enum SPlusType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Digital
|
|
||||||
/// </summary>
|
|
||||||
Digital,
|
|
||||||
/// <summary>
|
|
||||||
/// Analog
|
|
||||||
/// </summary>
|
|
||||||
Analog,
|
|
||||||
/// <summary>
|
|
||||||
/// String
|
|
||||||
/// </summary>
|
|
||||||
String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
//using PepperDash.Core;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The global class to manage all the instances of JsonToSimplMaster
|
|
||||||
/// </summary>
|
|
||||||
public class J2SGlobal
|
|
||||||
{
|
|
||||||
static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>();
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a file master. If the master's key or filename is equivalent to any existing
|
|
||||||
/// master, this will fail
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="master">New master to add</param>
|
|
||||||
///
|
|
||||||
public static void AddMaster(JsonToSimplMaster master)
|
|
||||||
{
|
|
||||||
if (master == null)
|
|
||||||
throw new ArgumentNullException("master");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(master.UniqueID))
|
|
||||||
throw new InvalidOperationException("JSON Master cannot be added with a null UniqueId");
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "JSON Global adding master {0}", master.UniqueID);
|
|
||||||
|
|
||||||
if (Masters.Contains(master)) return;
|
|
||||||
|
|
||||||
var existing = Masters.FirstOrDefault(m =>
|
|
||||||
m.UniqueID.Equals(master.UniqueID, StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (existing == null)
|
|
||||||
{
|
|
||||||
Masters.Add(master);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var msg = string.Format("Cannot add JSON Master with unique ID '{0}'.\rID is already in use on another master.", master.UniqueID);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Warn(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a master by its key. Case-insensitive
|
|
||||||
/// </summary>
|
|
||||||
public static JsonToSimplMaster GetMasterByFile(string file)
|
|
||||||
{
|
|
||||||
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Used to interact with an array of values with the S+ modules
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string SearchPropertyName { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string SearchPropertyValue { get; set; }
|
|
||||||
|
|
||||||
int ArrayIndex;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For gt2.4.1 array lookups
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file"></param>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="pathPrefix"></param>
|
|
||||||
/// <param name="pathSuffix"></param>
|
|
||||||
/// <param name="searchPropertyName"></param>
|
|
||||||
/// <param name="searchPropertyValue"></param>
|
|
||||||
public void Initialize(string file, string key, string pathPrefix, string pathSuffix,
|
|
||||||
string searchPropertyName, string searchPropertyValue)
|
|
||||||
{
|
|
||||||
base.Initialize(file, key, pathPrefix, pathSuffix);
|
|
||||||
SearchPropertyName = searchPropertyName;
|
|
||||||
SearchPropertyValue = searchPropertyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For newer >=2.4.1 array lookups.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file"></param>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="pathPrefix"></param>
|
|
||||||
/// <param name="pathAppend"></param>
|
|
||||||
/// <param name="pathSuffix"></param>
|
|
||||||
/// <param name="searchPropertyName"></param>
|
|
||||||
/// <param name="searchPropertyValue"></param>
|
|
||||||
public void InitializeWithAppend(string file, string key, string pathPrefix, string pathAppend,
|
|
||||||
string pathSuffix, string searchPropertyName, string searchPropertyValue)
|
|
||||||
{
|
|
||||||
string pathPrefixWithAppend = (pathPrefix != null ? pathPrefix : "") + GetPathAppend(pathAppend);
|
|
||||||
base.Initialize(file, key, pathPrefixWithAppend, pathSuffix);
|
|
||||||
|
|
||||||
SearchPropertyName = searchPropertyName;
|
|
||||||
SearchPropertyValue = searchPropertyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//PathPrefix+ArrayName+[x]+path+PathSuffix
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected override string GetFullPath(string path)
|
|
||||||
{
|
|
||||||
return string.Format("{0}[{1}].{2}{3}",
|
|
||||||
PathPrefix == null ? "" : PathPrefix,
|
|
||||||
ArrayIndex,
|
|
||||||
path,
|
|
||||||
PathSuffix == null ? "" : PathSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process all values
|
|
||||||
/// </summary>
|
|
||||||
public override void ProcessAll()
|
|
||||||
{
|
|
||||||
if (FindInArray())
|
|
||||||
base.ProcessAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides the path append for GetFullPath
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
string GetPathAppend(string a)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(a))
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (a.StartsWith("."))
|
|
||||||
{
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "." + a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
bool FindInArray()
|
|
||||||
{
|
|
||||||
if (Master == null)
|
|
||||||
throw new InvalidOperationException("Cannot do operations before master is linked");
|
|
||||||
if (Master.JsonObject == null)
|
|
||||||
throw new InvalidOperationException("Cannot do operations before master JSON has read");
|
|
||||||
if (PathPrefix == null)
|
|
||||||
throw new InvalidOperationException("Cannot do operations before PathPrefix is set");
|
|
||||||
|
|
||||||
|
|
||||||
var token = Master.JsonObject.SelectToken(PathPrefix);
|
|
||||||
if (token is JArray)
|
|
||||||
{
|
|
||||||
var array = token as JArray;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var item = array.FirstOrDefault(o =>
|
|
||||||
{
|
|
||||||
var prop = o[SearchPropertyName];
|
|
||||||
return prop != null && prop.Value<string>()
|
|
||||||
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
|
||||||
});
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug,"JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
|
|
||||||
PathPrefix, SearchPropertyName, SearchPropertyValue);
|
|
||||||
this.LinkedToObject = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.LinkedToObject = true;
|
|
||||||
ArrayIndex = array.IndexOf(item);
|
|
||||||
OnStringChange(string.Format("{0}[{1}]", PathPrefix, ArrayIndex), 0, JsonToSimplConstants.FullPathToArrayChange);
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(e, "JSON Child[{key}] Array '{pathPrefix}' lookup error: '{searchPropertyName}={searchPropertyValue}'", null, Key,
|
|
||||||
PathPrefix, SearchPropertyName, SearchPropertyValue, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "JSON Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Base class for JSON objects
|
|
||||||
/// </summary>
|
|
||||||
public abstract class JsonToSimplChildObjectBase : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of bool change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of ushort change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<UshrtChangeEventArgs> UShortChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of string change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate to get all values
|
|
||||||
/// </summary>
|
|
||||||
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Use a callback to reduce task switch/threading
|
|
||||||
/// </summary>
|
|
||||||
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unique identifier for instance
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This will be prepended to all paths to allow path swapping or for more organized
|
|
||||||
/// sub-paths
|
|
||||||
/// </summary>
|
|
||||||
public string PathPrefix { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is added to the end of all paths
|
|
||||||
/// </summary>
|
|
||||||
public string PathSuffix { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if the instance is linked to an object
|
|
||||||
/// </summary>
|
|
||||||
public bool LinkedToObject { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference to Master instance
|
|
||||||
/// </summary>
|
|
||||||
protected JsonToSimplMaster Master;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Paths to boolean values in JSON structure
|
|
||||||
/// </summary>
|
|
||||||
protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>();
|
|
||||||
/// <summary>
|
|
||||||
/// Paths to numeric values in JSON structure
|
|
||||||
/// </summary>
|
|
||||||
protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>();
|
|
||||||
/// <summary>
|
|
||||||
/// Paths to string values in JSON structure
|
|
||||||
/// </summary>
|
|
||||||
protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call this before doing anything else
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="masterUniqueId"></param>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="pathPrefix"></param>
|
|
||||||
/// <param name="pathSuffix"></param>
|
|
||||||
public void Initialize(string masterUniqueId, string key, string pathPrefix, string pathSuffix)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
PathPrefix = pathPrefix;
|
|
||||||
PathSuffix = pathSuffix;
|
|
||||||
|
|
||||||
Master = J2SGlobal.GetMasterByFile(masterUniqueId);
|
|
||||||
if (Master != null)
|
|
||||||
Master.AddChild(this);
|
|
||||||
else
|
|
||||||
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the path prefix for the object
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pathPrefix"></param>
|
|
||||||
public void SetPathPrefix(string pathPrefix)
|
|
||||||
{
|
|
||||||
PathPrefix = pathPrefix;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Set the JPath to evaluate for a given bool out index.
|
|
||||||
/// </summary>
|
|
||||||
public void SetBoolPath(ushort index, string path)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
|
||||||
BoolPaths[index] = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the JPath for a ushort out index.
|
|
||||||
/// </summary>
|
|
||||||
public void SetUshortPath(ushort index, string path)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
|
||||||
UshortPaths[index] = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the JPath for a string output index.
|
|
||||||
/// </summary>
|
|
||||||
public void SetStringPath(ushort index, string path)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
|
||||||
StringPaths[index] = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evalutates all outputs with defined paths. called by S+ when paths are ready to process
|
|
||||||
/// and by Master when file is read.
|
|
||||||
/// </summary>
|
|
||||||
public virtual void ProcessAll()
|
|
||||||
{
|
|
||||||
if (!LinkedToObject)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SetAllPathsDelegate();
|
|
||||||
foreach (var kvp in BoolPaths)
|
|
||||||
ProcessBoolPath(kvp.Key);
|
|
||||||
foreach (var kvp in UshortPaths)
|
|
||||||
ProcessUshortPath(kvp.Key);
|
|
||||||
foreach (var kvp in StringPaths)
|
|
||||||
ProcessStringPath(kvp.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes a bool property, converting to bool, firing off a BoolChange event
|
|
||||||
/// </summary>
|
|
||||||
void ProcessBoolPath(ushort index)
|
|
||||||
{
|
|
||||||
string response;
|
|
||||||
if (Process(BoolPaths[index], out response))
|
|
||||||
OnBoolChange(response.Equals("true", StringComparison.OrdinalIgnoreCase),
|
|
||||||
index, JsonToSimplConstants.BoolValueChange);
|
|
||||||
else { }
|
|
||||||
// OnBoolChange(false, index, JsonToSimplConstants.BoolValueChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event
|
|
||||||
void ProcessUshortPath(ushort index) {
|
|
||||||
string response;
|
|
||||||
if (Process(UshortPaths[index], out response)) {
|
|
||||||
ushort val;
|
|
||||||
try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); }
|
|
||||||
catch { val = 0; }
|
|
||||||
|
|
||||||
OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange);
|
|
||||||
}
|
|
||||||
else { }
|
|
||||||
// OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes the path to a string property and fires of a StringChange event.
|
|
||||||
void ProcessStringPath(ushort index)
|
|
||||||
{
|
|
||||||
string response;
|
|
||||||
if (Process(StringPaths[index], out response))
|
|
||||||
OnStringChange(response, index, JsonToSimplConstants.StringValueChange);
|
|
||||||
else { }
|
|
||||||
// OnStringChange("", index, JsonToSimplConstants.StringValueChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the given path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">JPath formatted path to the desired property</param>
|
|
||||||
/// <param name="response">The string value of the property, or a default value if it
|
|
||||||
/// doesn't exist</param>
|
|
||||||
/// <returns> This will return false in the case that EvaulateAllOnJsonChange
|
|
||||||
/// is false and the path does not evaluate to a property in the incoming JSON. </returns>
|
|
||||||
bool Process(string path, out string response)
|
|
||||||
{
|
|
||||||
path = GetFullPath(path);
|
|
||||||
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
|
|
||||||
response = "";
|
|
||||||
if (Master == null)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Master.JsonObject != null && path != string.Empty)
|
|
||||||
{
|
|
||||||
bool isCount = false;
|
|
||||||
path = path.Trim();
|
|
||||||
if (path.EndsWith(".Count"))
|
|
||||||
{
|
|
||||||
path = path.Remove(path.Length - 6, 6);
|
|
||||||
isCount = true;
|
|
||||||
}
|
|
||||||
try // Catch a strange cast error on a bad path
|
|
||||||
{
|
|
||||||
var t = Master.JsonObject.SelectToken(path);
|
|
||||||
if (t != null)
|
|
||||||
{
|
|
||||||
// return the count of children objects - if any
|
|
||||||
if (isCount)
|
|
||||||
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
|
||||||
else
|
|
||||||
response = t.Value<string>();
|
|
||||||
Debug.Console(1, " ='{0}'", response);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
response = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the path isn't found, return this to determine whether to pass out the non-value or not.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//************************************************************************************************
|
|
||||||
// Save-related functions
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from Master to read inputs and update their values in master JObject
|
|
||||||
/// Callback should hit one of the following four methods
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateInputsForMaster()
|
|
||||||
{
|
|
||||||
if (!LinkedToObject)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SetAllPathsDelegate();
|
|
||||||
var del = GetAllValuesDelegate;
|
|
||||||
if (del != null)
|
|
||||||
GetAllValuesDelegate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="theValue"></param>
|
|
||||||
public void USetBoolValue(ushort key, ushort theValue)
|
|
||||||
{
|
|
||||||
SetBoolValue(key, theValue == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="theValue"></param>
|
|
||||||
public void SetBoolValue(ushort key, bool theValue)
|
|
||||||
{
|
|
||||||
if (BoolPaths.ContainsKey(key))
|
|
||||||
SetValueOnMaster(BoolPaths[key], new JValue(theValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="theValue"></param>
|
|
||||||
public void SetUShortValue(ushort key, ushort theValue)
|
|
||||||
{
|
|
||||||
if (UshortPaths.ContainsKey(key))
|
|
||||||
SetValueOnMaster(UshortPaths[key], new JValue(theValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="theValue"></param>
|
|
||||||
public void SetStringValue(ushort key, string theValue)
|
|
||||||
{
|
|
||||||
if (StringPaths.ContainsKey(key))
|
|
||||||
SetValueOnMaster(StringPaths[key], new JValue(theValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="keyPath"></param>
|
|
||||||
/// <param name="valueToSave"></param>
|
|
||||||
public void SetValueOnMaster(string keyPath, JValue valueToSave)
|
|
||||||
{
|
|
||||||
var path = GetFullPath(keyPath);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
|
||||||
|
|
||||||
//var token = Master.JsonObject.SelectToken(path);
|
|
||||||
//if (token != null) // The path exists in the file
|
|
||||||
Master.AddUnsavedValue(path, valueToSave);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called during Process(...) to get the path to a given property. By default,
|
|
||||||
/// returns PathPrefix+path+PathSuffix. Override to change the way path is built.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual string GetFullPath(string path)
|
|
||||||
{
|
|
||||||
return (PathPrefix != null ? PathPrefix : "") +
|
|
||||||
path + (PathSuffix != null ? PathSuffix : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers for events
|
|
||||||
//******************************************************************************************
|
|
||||||
/// <summary>
|
|
||||||
/// Event helper
|
|
||||||
/// </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>
|
|
||||||
/// Event helper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnUShortChange(ushort state, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = UShortChange;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new UshrtChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
UShortChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event helper
|
|
||||||
/// </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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a JSON file that can be read and written to
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplFileMaster : JsonToSimplMaster
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the filepath as well as registers this with the Global.Masters list
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
/// Read, evaluate and udpate status
|
|
||||||
/// </summary>
|
|
||||||
public void EvaluateFile(string filepath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
|
|
||||||
var dirSeparator = Path.DirectorySeparatorChar;
|
|
||||||
var dirSeparatorAlt = Path.AltDirectorySeparatorChar;
|
|
||||||
|
|
||||||
var series = CrestronEnvironment.ProgramCompatibility;
|
|
||||||
|
|
||||||
var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3));
|
|
||||||
OnBoolChange(is3Series, 0,
|
|
||||||
JsonToSimplConstants.ProgramCompatibility3SeriesChange);
|
|
||||||
|
|
||||||
var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4));
|
|
||||||
OnBoolChange(is4Series, 0,
|
|
||||||
JsonToSimplConstants.ProgramCompatibility4SeriesChange);
|
|
||||||
|
|
||||||
var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server;
|
|
||||||
OnBoolChange(isServer, 0,
|
|
||||||
JsonToSimplConstants.DevicePlatformValueChange);
|
|
||||||
|
|
||||||
// get the roomID
|
|
||||||
var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId;
|
|
||||||
if (!string.IsNullOrEmpty(roomId))
|
|
||||||
{
|
|
||||||
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the roomName
|
|
||||||
var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName;
|
|
||||||
if (!string.IsNullOrEmpty(roomName))
|
|
||||||
{
|
|
||||||
OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootDirectory = Directory.GetApplicationRootDirectory();
|
|
||||||
OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange);
|
|
||||||
|
|
||||||
var splusPath = string.Empty;
|
|
||||||
if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase))
|
|
||||||
{
|
|
||||||
if (is4Series)
|
|
||||||
splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase);
|
|
||||||
else if (isServer)
|
|
||||||
splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase);
|
|
||||||
else
|
|
||||||
splusPath = filepath;
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator);
|
|
||||||
|
|
||||||
Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory,
|
|
||||||
filepath.TrimStart(dirSeparator, dirSeparatorAlt));
|
|
||||||
|
|
||||||
OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Filepath))
|
|
||||||
{
|
|
||||||
OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get file directory and name to search
|
|
||||||
var fileDirectory = Path.GetDirectoryName(Filepath);
|
|
||||||
var fileName = Path.GetFileName(Filepath);
|
|
||||||
|
|
||||||
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
|
|
||||||
|
|
||||||
if (Directory.Exists(fileDirectory))
|
|
||||||
{
|
|
||||||
// get the directory info
|
|
||||||
var directoryInfo = new DirectoryInfo(fileDirectory);
|
|
||||||
|
|
||||||
// get the file to be read
|
|
||||||
var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault();
|
|
||||||
if (actualFile == null)
|
|
||||||
{
|
|
||||||
var msg = string.Format("JSON file not found: {0}", Filepath);
|
|
||||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
|
|
||||||
// \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
|
|
||||||
ActualFilePath = actualFile.FullName;
|
|
||||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
|
||||||
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
|
|
||||||
|
|
||||||
Filename = actualFile.Name;
|
|
||||||
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
|
|
||||||
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "JSON Filename is {0}", Filename);
|
|
||||||
|
|
||||||
|
|
||||||
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
|
|
||||||
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
|
||||||
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "JSON File Path is {0}", FilePathName);
|
|
||||||
|
|
||||||
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
|
||||||
|
|
||||||
JsonObject = JObject.Parse(json);
|
|
||||||
foreach (var child in Children)
|
|
||||||
child.ProcessAll();
|
|
||||||
|
|
||||||
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
Debug.Console(1, "'{0}' not found", fileDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
|
|
||||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
|
|
||||||
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
|
||||||
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
|
|
||||||
CrestronConsole.PrintLine(stackTrace);
|
|
||||||
ErrorLog.Error(stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
|
||||||
child.UpdateInputsForMaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sw.Write(JsonObject.ToString());
|
|
||||||
sw.Flush();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
|
||||||
Debug.Console(0, err);
|
|
||||||
ErrorLog.Warn(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public JsonToSimplFixedPathObject()
|
|
||||||
{
|
|
||||||
this.LinkedToObject = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic Master
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplGenericMaster : JsonToSimplMaster
|
|
||||||
{
|
|
||||||
/*****************************************************************************************/
|
|
||||||
/** Privates **/
|
|
||||||
|
|
||||||
|
|
||||||
// The JSON file in JObject form
|
|
||||||
// For gathering the incoming data
|
|
||||||
object StringBuilderLock = new object();
|
|
||||||
// To prevent multiple same-file access
|
|
||||||
static object WriteLock = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback action for saving
|
|
||||||
/// </summary>
|
|
||||||
public Action<string> SaveCallback { get; set; }
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SIMPL+ default constructor.
|
|
||||||
/// </summary>
|
|
||||||
public JsonToSimplGenericMaster()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads in JSON and triggers evaluation on all children
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json"></param>
|
|
||||||
public void LoadWithJson(string json)
|
|
||||||
{
|
|
||||||
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JsonObject = JObject.Parse(json);
|
|
||||||
foreach (var child in Children)
|
|
||||||
child.ProcessAll();
|
|
||||||
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads JSON into JsonObject, but does not trigger evaluation by children
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json"></param>
|
|
||||||
public void SetJsonWithoutEvaluating(string json)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JsonObject = JObject.Parse(json);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
|
|
||||||
child.UpdateInputsForMaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Master. No updated values to save. Skipping");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (WriteLock)
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Saving");
|
|
||||||
foreach (var path in UnsavedValues.Keys)
|
|
||||||
{
|
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
|
||||||
if (tokenToReplace != null)
|
|
||||||
{// It's found
|
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
|
||||||
Debug.Console(1, this, "Master Updating '{0}'", path);
|
|
||||||
}
|
|
||||||
else // No token. Let's make one
|
|
||||||
{
|
|
||||||
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (SaveCallback != null)
|
|
||||||
SaveCallback(JsonObject.ToString());
|
|
||||||
else
|
|
||||||
Debug.Console(0, this, "WARNING: No save callback defined.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract base class for JsonToSimpl interactions
|
|
||||||
/// </summary>
|
|
||||||
public abstract class JsonToSimplMaster : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of bool change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of ushort change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of string change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A collection of associated child modules
|
|
||||||
/// </summary>
|
|
||||||
protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>();
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mirrors the Unique ID for now.
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get { return UniqueID; } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A unique ID
|
|
||||||
/// </summary>
|
|
||||||
public string UniqueID { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Merely for use in debug messages
|
|
||||||
/// </summary>
|
|
||||||
public string DebugName
|
|
||||||
{
|
|
||||||
get { return _DebugName; }
|
|
||||||
set { if (DebugName == null) _DebugName = ""; else _DebugName = value; }
|
|
||||||
}
|
|
||||||
string _DebugName = "";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This will be prepended to all paths to allow path swapping or for more organized
|
|
||||||
/// sub-paths
|
|
||||||
/// </summary>
|
|
||||||
public string PathPrefix { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is added to the end of all paths
|
|
||||||
/// </summary>
|
|
||||||
public string PathSuffix { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enables debugging output to the console. Certain error messages will be logged to the
|
|
||||||
/// system's error log regardless of this setting
|
|
||||||
/// </summary>
|
|
||||||
public bool DebugOn { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ushort helper for Debug property
|
|
||||||
/// </summary>
|
|
||||||
public ushort UDebug
|
|
||||||
{
|
|
||||||
get { return (ushort)(DebugOn ? 1 : 0); }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
DebugOn = (value == 1);
|
|
||||||
CrestronConsole.PrintLine("JsonToSimpl debug={0}", DebugOn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public JObject JsonObject { get; protected set; }
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
/** Privates **/
|
|
||||||
|
|
||||||
|
|
||||||
// The JSON file in JObject form
|
|
||||||
// For gathering the incoming data
|
|
||||||
protected Dictionary<string, JValue> UnsavedValues = new Dictionary<string, JValue>();
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SIMPL+ default constructor.
|
|
||||||
/// </summary>
|
|
||||||
public JsonToSimplMaster()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets up class - overriding methods should always call this.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uniqueId"></param>
|
|
||||||
public virtual void Initialize(string uniqueId)
|
|
||||||
{
|
|
||||||
UniqueID = uniqueId;
|
|
||||||
J2SGlobal.AddMaster(this); // Should not re-add
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a child "module" to this master
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="child"></param>
|
|
||||||
public void AddChild(JsonToSimplChildObjectBase child)
|
|
||||||
{
|
|
||||||
if (!Children.Contains(child))
|
|
||||||
{
|
|
||||||
Children.Add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from the child to add changed or new values for saving
|
|
||||||
/// </summary>
|
|
||||||
public void AddUnsavedValue(string path, JValue value)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
UnsavedValues.Add(path, value);
|
|
||||||
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Saves the file
|
|
||||||
/// </summary>
|
|
||||||
public abstract void Save();
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public static class JsonFixes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Deserializes a string into a JObject
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static JObject ParseObject(string json)
|
|
||||||
{
|
|
||||||
#if NET6_0
|
|
||||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
|
||||||
#else
|
|
||||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
var startDepth = reader.Depth;
|
|
||||||
var obj = JObject.Load(reader);
|
|
||||||
if (startDepth != reader.Depth)
|
|
||||||
throw new JsonSerializationException("Unenclosed json found");
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserializes a string into a JArray
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static JArray ParseArray(string json)
|
|
||||||
{
|
|
||||||
#if NET6_0
|
|
||||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
|
||||||
#else
|
|
||||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
var startDepth = reader.Depth;
|
|
||||||
var obj = JArray.Load(reader);
|
|
||||||
if (startDepth != reader.Depth)
|
|
||||||
throw new JsonSerializationException("Unenclosed json found");
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
if (BoolChange != null)
|
|
||||||
{
|
|
||||||
var args = new BoolChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
BoolChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnUshrtChange(ushort state, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
if (UshrtChange != null)
|
|
||||||
{
|
|
||||||
var args = new UshrtChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
UshrtChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnStringChange(string value, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
if (StringChange != null)
|
|
||||||
{
|
|
||||||
var args = new StringChangeEventArgs(value, type);
|
|
||||||
args.Index = index;
|
|
||||||
StringChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Core.Config;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Portal File Master
|
|
||||||
/// </summary>
|
|
||||||
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the filepath as well as registers this with the Global.Masters list
|
|
||||||
/// </summary>
|
|
||||||
public string PortalFilepath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File path of the actual file being read (Portal or local)
|
|
||||||
/// </summary>
|
|
||||||
public string ActualFilePath { get; private set; }
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
/** Privates **/
|
|
||||||
|
|
||||||
// To prevent multiple same-file access
|
|
||||||
object StringBuilderLock = new object();
|
|
||||||
static object FileLock = new object();
|
|
||||||
|
|
||||||
/*****************************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SIMPL+ default constructor.
|
|
||||||
/// </summary>
|
|
||||||
public JsonToSimplPortalFileMaster()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read, evaluate and udpate status
|
|
||||||
/// </summary>
|
|
||||||
public void EvaluateFile(string portalFilepath)
|
|
||||||
{
|
|
||||||
PortalFilepath = portalFilepath;
|
|
||||||
|
|
||||||
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
if (string.IsNullOrEmpty(PortalFilepath))
|
|
||||||
{
|
|
||||||
CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve possible wildcarded filename
|
|
||||||
|
|
||||||
// If the portal file is xyz.json, then
|
|
||||||
// the file we want to check for first will be called xyz.local.json
|
|
||||||
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
|
||||||
Debug.Console(0, this, "Checking for local file {0}", localFilepath);
|
|
||||||
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
|
||||||
|
|
||||||
if (actualLocalFile != null)
|
|
||||||
{
|
|
||||||
ActualFilePath = actualLocalFile.FullName;
|
|
||||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
|
||||||
}
|
|
||||||
// If the local file does not exist, then read the portal file xyz.json
|
|
||||||
// and create the local.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
|
||||||
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
|
||||||
if (actualPortalFile != null)
|
|
||||||
{
|
|
||||||
var newLocalPath = Path.ChangeExtension(actualPortalFile.FullName, "local.json");
|
|
||||||
// got the portal file, hand off to the merge / save method
|
|
||||||
PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath);
|
|
||||||
ActualFilePath = newLocalPath;
|
|
||||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
|
||||||
Debug.Console(1, this, msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point we should have a local file. Do it.
|
|
||||||
Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
|
|
||||||
|
|
||||||
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JsonObject = JObject.Parse(json);
|
|
||||||
foreach (var child in Children)
|
|
||||||
child.ProcessAll();
|
|
||||||
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
ErrorLog.Error(msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the FileInfo object for a given path, with possible wildcards
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
FileInfo GetActualFileInfoFromPath(string path)
|
|
||||||
{
|
|
||||||
var dir = Path.GetDirectoryName(path);
|
|
||||||
var localFilename = Path.GetFileName(path);
|
|
||||||
var directory = new DirectoryInfo(dir);
|
|
||||||
// search the directory for the file w/ wildcards
|
|
||||||
return directory.GetFiles(localFilename).FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
public void setDebugLevel(uint level)
|
|
||||||
{
|
|
||||||
Debug.SetDebugLevel(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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)
|
|
||||||
{
|
|
||||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
|
||||||
child.UpdateInputsForMaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sw.Write(JsonObject.ToString());
|
|
||||||
sw.Flush();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
|
||||||
Debug.Console(0, err);
|
|
||||||
ErrorLog.Warn(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
|
||||||
{
|
|
||||||
public class CrestronEnricher : ILogEventEnricher
|
|
||||||
{
|
|
||||||
static readonly string _appName;
|
|
||||||
|
|
||||||
static CrestronEnricher()
|
|
||||||
{
|
|
||||||
switch (CrestronEnvironment.DevicePlatform)
|
|
||||||
{
|
|
||||||
case eDevicePlatform.Appliance:
|
|
||||||
_appName = $"App {InitialParametersClass.ApplicationNumber}";
|
|
||||||
break;
|
|
||||||
case eDevicePlatform.Server:
|
|
||||||
_appName = $"{InitialParametersClass.RoomId}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
@@ -1,55 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
using Serilog.Configuration;
|
|
||||||
using Serilog;
|
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
using Serilog.Formatting;
|
|
||||||
using Serilog.Formatting.Json;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
public class DebugConsoleSink : ILogEventSink
|
|
||||||
{
|
|
||||||
private readonly ITextFormatter _textFormatter;
|
|
||||||
|
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
{
|
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
|
||||||
|
|
||||||
/*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
|
|
||||||
|
|
||||||
if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
|
||||||
{
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var buffer = new StringWriter(new StringBuilder(256));
|
|
||||||
|
|
||||||
_textFormatter.Format(logEvent, buffer);
|
|
||||||
|
|
||||||
var message = buffer.ToString();
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugConsoleSink(ITextFormatter formatProvider )
|
|
||||||
{
|
|
||||||
_textFormatter = formatProvider ?? new JsonFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DebugConsoleSinkExtensions
|
|
||||||
{
|
|
||||||
public static LoggerConfiguration DebugConsoleSink(
|
|
||||||
this LoggerSinkConfiguration loggerConfiguration,
|
|
||||||
ITextFormatter formatProvider = null)
|
|
||||||
{
|
|
||||||
return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,281 +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>
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
/// Appends a device Key to the beginning of a message
|
|
||||||
/// </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>
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.CrestronLogger;
|
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
|
||||||
{
|
|
||||||
public class DebugCrestronLoggerSink : ILogEventSink
|
|
||||||
{
|
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
{
|
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
|
||||||
|
|
||||||
string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
|
|
||||||
|
|
||||||
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
|
||||||
{
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}";
|
|
||||||
}
|
|
||||||
|
|
||||||
CrestronLogger.WriteToLog(message, (uint)logEvent.Level);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugCrestronLoggerSink()
|
|
||||||
{
|
|
||||||
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
using Serilog.Formatting;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
|
||||||
{
|
|
||||||
public class DebugErrorLogSink : ILogEventSink
|
|
||||||
{
|
|
||||||
private ITextFormatter _formatter;
|
|
||||||
|
|
||||||
private Dictionary<LogEventLevel, Action<string>> _errorLogMap = new Dictionary<LogEventLevel, Action<string>>
|
|
||||||
{
|
|
||||||
{ 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;
|
|
||||||
|
|
||||||
if (_formatter == null)
|
|
||||||
{
|
|
||||||
var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
|
||||||
? $"App {InitialParametersClass.ApplicationNumber}"
|
|
||||||
: $"Room {InitialParametersClass.RoomId}";
|
|
||||||
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}";
|
|
||||||
|
|
||||||
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
|
|
||||||
{
|
|
||||||
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}";
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
var buffer = new StringWriter(new StringBuilder(256));
|
|
||||||
|
|
||||||
_formatter.Format(logEvent, buffer);
|
|
||||||
|
|
||||||
message = buffer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugErrorLogSink(ITextFormatter formatter = null)
|
|
||||||
{
|
|
||||||
_formatter = formatter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using Serilog.Events;
|
|
||||||
using System;
|
|
||||||
using Log = PepperDash.Core.Debug;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
|
||||||
{
|
|
||||||
public static class DebugExtensions
|
|
||||||
{
|
|
||||||
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogVerbose(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Verbose, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Debug, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogDebug(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Debug, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Information, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogInformation(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Information, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Warning, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogWarning(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Warning, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Error, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogError(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Error, device, message, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
|
||||||
{
|
|
||||||
Log.LogMessage(LogEventLevel.Fatal, device, message, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class to persist current Debug settings across program restarts
|
|
||||||
/// </summary>
|
|
||||||
public class DebugContextCollection
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// To prevent threading issues with the DeviceDebugSettings collection
|
|
||||||
/// </summary>
|
|
||||||
private readonly CCriticalSection _deviceDebugSettingsLock;
|
|
||||||
|
|
||||||
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Collection of the debug settings for each device where the dictionary key is the device key
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("deviceDebugSettings")]
|
|
||||||
private Dictionary<string, object> DeviceDebugSettings { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
public void SetLevel(string contextKey, int level)
|
|
||||||
{
|
|
||||||
if (level < 0 || level > 2)
|
|
||||||
return;
|
|
||||||
GetOrCreateItem(contextKey).Level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a level or creates it if not existing
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="contextKey"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public DebugContextItem GetOrCreateItem(string contextKey)
|
|
||||||
{
|
|
||||||
if (!_items.ContainsKey(contextKey))
|
|
||||||
_items[contextKey] = new DebugContextItem { Level = 0 };
|
|
||||||
return _items[contextKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// sets the settings for a device or creates a new entry
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deviceKey"></param>
|
|
||||||
/// <param name="settings"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_deviceDebugSettingsLock.Enter();
|
|
||||||
|
|
||||||
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
|
||||||
{
|
|
||||||
DeviceDebugSettings[deviceKey] = settings;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
DeviceDebugSettings.Add(deviceKey, settings);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_deviceDebugSettingsLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the device settings for a device by key or returns null
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="deviceKey"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public object GetDebugSettingsForKey(string deviceKey)
|
|
||||||
{
|
|
||||||
return DeviceDebugSettings[deviceKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains information about
|
|
||||||
/// </summary>
|
|
||||||
public class DebugContextItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The level of debug messages to print
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("level")]
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Property to tell the program not to intitialize when it boots, if desired
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("doNotLoadOnNextBoot")]
|
|
||||||
public bool DoNotLoadOnNextBoot { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Serilog;
|
|
||||||
using Serilog.Core;
|
|
||||||
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 Newtonsoft.Json.Linq;
|
|
||||||
using Serilog.Formatting.Json;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
public class DebugWebsocketSink : ILogEventSink
|
|
||||||
{
|
|
||||||
private HttpServer _httpsServer;
|
|
||||||
|
|
||||||
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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
|
||||||
|
|
||||||
|
|
||||||
private readonly ITextFormatter _textFormatter;
|
|
||||||
|
|
||||||
public DebugWebsocketSink(ITextFormatter formatProvider)
|
|
||||||
{
|
|
||||||
|
|
||||||
_textFormatter = formatProvider ?? new JsonFormatter();
|
|
||||||
|
|
||||||
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
|
|
||||||
CreateCert(null);
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
|
||||||
{
|
|
||||||
if (type == eProgramStatusEventType.Stopping)
|
|
||||||
{
|
|
||||||
StopServer();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateCert(string[] args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Emit(LogEvent logEvent)
|
|
||||||
{
|
|
||||||
if (_httpsServer == null || !_httpsServer.IsListening) return;
|
|
||||||
|
|
||||||
var sw = new StringWriter();
|
|
||||||
_textFormatter.Format(logEvent, sw);
|
|
||||||
|
|
||||||
_httpsServer.WebSocketServices.Broadcast(sw.ToString());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartServerAndSetPort(int port)
|
|
||||||
{
|
|
||||||
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
|
|
||||||
|
|
||||||
|
|
||||||
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Start(int port, string certPath = "", string certPassword = "")
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
CheckCertificateRevocation = false,
|
|
||||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
|
|
||||||
//this is just to test, you might want to actually validate
|
|
||||||
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
|
||||||
{
|
|
||||||
Debug.Console(0, "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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopServer()
|
|
||||||
{
|
|
||||||
Debug.Console(0, "Stopping Websocket Server");
|
|
||||||
_httpsServer?.Stop();
|
|
||||||
|
|
||||||
_httpsServer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DebugWebsocketSinkExtensions
|
|
||||||
{
|
|
||||||
public static LoggerConfiguration DebugWebsocketSink(
|
|
||||||
this LoggerSinkConfiguration loggerConfiguration,
|
|
||||||
ITextFormatter formatProvider = null)
|
|
||||||
{
|
|
||||||
return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DebugClient : WebSocketBehavior
|
|
||||||
{
|
|
||||||
private DateTime _connectionTime;
|
|
||||||
|
|
||||||
public TimeSpan ConnectedDuration
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Context.WebSocket.IsAlive)
|
|
||||||
{
|
|
||||||
return DateTime.Now - _connectionTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new TimeSpan(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebugClient()
|
|
||||||
{
|
|
||||||
Debug.Console(0, "DebugClient Created");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnOpen()
|
|
||||||
{
|
|
||||||
base.OnOpen();
|
|
||||||
|
|
||||||
var url = Context.WebSocket.Url;
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
|
|
||||||
|
|
||||||
_connectionTime = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMessage(MessageEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnMessage(e);
|
|
||||||
|
|
||||||
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClose(CloseEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnClose(e);
|
|
||||||
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnError(e);
|
|
||||||
|
|
||||||
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Not in use
|
|
||||||
/// </summary>
|
|
||||||
public static class NetworkComm
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Not in use
|
|
||||||
/// </summary>
|
|
||||||
static NetworkComm()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// JSON password configuration
|
|
||||||
/// </summary>
|
|
||||||
public class PasswordConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Password object configured password
|
|
||||||
/// </summary>
|
|
||||||
public string password { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public PasswordConfig()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants
|
|
||||||
/// </summary>
|
|
||||||
public class PasswordManagementConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generic boolean value change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort BoolValueChange = 1;
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluated boolean change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordInitializedChange = 2;
|
|
||||||
/// <summary>
|
|
||||||
/// Update busy change const
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordUpdateBusyChange = 3;
|
|
||||||
/// <summary>
|
|
||||||
/// Password is valid change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordValidationChange = 4;
|
|
||||||
/// <summary>
|
|
||||||
/// Password LED change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordLedFeedbackChange = 5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generic ushort value change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort UshrtValueChange = 101;
|
|
||||||
/// <summary>
|
|
||||||
/// Password count
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordManagerCountChange = 102;
|
|
||||||
/// <summary>
|
|
||||||
/// Password selecte index change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordSelectIndexChange = 103;
|
|
||||||
/// <summary>
|
|
||||||
/// Password length
|
|
||||||
/// </summary>
|
|
||||||
public const ushort PasswordLengthChange = 104;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generic string value change constant
|
|
||||||
/// </summary>
|
|
||||||
public const ushort StringValueChange = 201;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class to allow user interaction with the PasswordManager
|
|
||||||
/// </summary>
|
|
||||||
public class PasswordClient
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Password selected
|
|
||||||
/// </summary>
|
|
||||||
public string Password { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Password selected key
|
|
||||||
/// </summary>
|
|
||||||
public ushort Key { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Used to build the password entered by the user
|
|
||||||
/// </summary>
|
|
||||||
public string PasswordToValidate { get; set; }
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public PasswordClient()
|
|
||||||
{
|
|
||||||
PasswordManager.PasswordChange += new EventHandler<StringChangeEventArgs>(PasswordManager_PasswordChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize method
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordInitializedChange);
|
|
||||||
|
|
||||||
Password = "";
|
|
||||||
PasswordToValidate = "";
|
|
||||||
|
|
||||||
OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
|
||||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve password by index
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
public void GetPasswordByIndex(ushort key)
|
|
||||||
{
|
|
||||||
OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
|
||||||
|
|
||||||
Key = key;
|
|
||||||
|
|
||||||
var pw = PasswordManager.Passwords[Key];
|
|
||||||
if (pw == null)
|
|
||||||
{
|
|
||||||
OnUshrtChange(0, 0, PasswordManagementConstants.PasswordLengthChange);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Password = pw;
|
|
||||||
OnUshrtChange((ushort)Password.Length, 0, PasswordManagementConstants.PasswordLengthChange);
|
|
||||||
OnUshrtChange(key, 0, PasswordManagementConstants.PasswordSelectIndexChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Password validation method
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
public void ValidatePassword(string password)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(password))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.Equals(Password, password))
|
|
||||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordValidationChange);
|
|
||||||
else
|
|
||||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordValidationChange);
|
|
||||||
|
|
||||||
ClearPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds the user entered passwrod string, will attempt to validate the user entered
|
|
||||||
/// password against the selected password when the length of the 2 are equal
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data"></param>
|
|
||||||
public void BuildPassword(string data)
|
|
||||||
{
|
|
||||||
PasswordToValidate = String.Concat(PasswordToValidate, data);
|
|
||||||
OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange);
|
|
||||||
|
|
||||||
if (PasswordToValidate.Length == Password.Length)
|
|
||||||
ValidatePassword(PasswordToValidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the user entered password and resets the LEDs
|
|
||||||
/// </summary>
|
|
||||||
public void ClearPassword()
|
|
||||||
{
|
|
||||||
PasswordToValidate = "";
|
|
||||||
OnBoolChange(false, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
var args = new BoolChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
BoolChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected ushort change event handler
|
|
||||||
/// </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 string change event handler
|
|
||||||
/// </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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If password changes while selected change event will be notifed and update the client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
protected void PasswordManager_PasswordChange(object sender, StringChangeEventArgs args)
|
|
||||||
{
|
|
||||||
//throw new NotImplementedException();
|
|
||||||
if (Key == args.Index)
|
|
||||||
{
|
|
||||||
//PasswordSelectedKey = args.Index;
|
|
||||||
//PasswordSelected = args.StringValue;
|
|
||||||
GetPasswordByIndex(args.Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Allows passwords to be stored and managed
|
|
||||||
/// </summary>
|
|
||||||
public class 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>
|
|
||||||
/// 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>
|
|
||||||
/// Initialize password manager
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
if (Passwords == null)
|
|
||||||
Passwords = new Dictionary<uint, string>();
|
|
||||||
|
|
||||||
if (_passwords == null)
|
|
||||||
_passwords = new Dictionary<uint, string>();
|
|
||||||
|
|
||||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates password stored in the dictonary
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
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));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped"));
|
|
||||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
|
||||||
foreach (var pw in _passwords)
|
|
||||||
{
|
|
||||||
// if key exists, continue
|
|
||||||
if (Passwords.ContainsKey(pw.Key))
|
|
||||||
{
|
|
||||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value));
|
|
||||||
if (Passwords[pw.Key] != _passwords[pw.Key])
|
|
||||||
{
|
|
||||||
Passwords[pw.Key] = _passwords[pw.Key];
|
|
||||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]));
|
|
||||||
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else add the key & value
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Passwords.Add(pw.Key, pw.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e);
|
|
||||||
Debug.Console(1, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Method to change the default timer value, (default 5000ms/5s)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="time"></param>
|
|
||||||
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.Console(0, "PasswordManager.ListPasswords:\r");
|
|
||||||
foreach (var pw in Passwords)
|
|
||||||
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
|
||||||
Debug.Console(0, "\n");
|
|
||||||
foreach (var pw in _passwords)
|
|
||||||
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
var args = new BoolChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
BoolChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected ushort change event handler
|
|
||||||
/// </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 string change event handler
|
|
||||||
/// </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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<ProjectType>Library</ProjectType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup>
|
|
||||||
<RootNamespace>PepperDash.Core</RootNamespace>
|
|
||||||
<AssemblyName>PepperDashCore</AssemblyName>
|
|
||||||
<TargetFramework>net472</TargetFramework>
|
|
||||||
<Deterministic>true</Deterministic>
|
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
|
||||||
<SignAssembly>False</SignAssembly>
|
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
|
||||||
<Title>PepperDash Core</Title>
|
|
||||||
<Company>PepperDash Technologies</Company>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<RepositoryUrl>https://github.com/PepperDash/PepperDashCore</RepositoryUrl>
|
|
||||||
<PackageTags>crestron;4series;</PackageTags>
|
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
|
||||||
<InformationalVersion>$(Version)</InformationalVersion>
|
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<DefineConstants>TRACE;DEBUG;SERIES4</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<DocumentationFile>bin\4Series\$(Configuration)\PepperDashCore.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="lib\**" />
|
|
||||||
<Compile Remove="Properties\**" />
|
|
||||||
<EmbeddedResource Remove="lib\**" />
|
|
||||||
<EmbeddedResource Remove="Properties\**" />
|
|
||||||
<None Remove="lib\**" />
|
|
||||||
<None Remove="Properties\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<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>
|
|
||||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
|
||||||
<Compile Remove="Comm\._GenericTcpIpClient.cs" />
|
|
||||||
<Compile Remove="Comm\DynamicTCPServer.cs" />
|
|
||||||
<Compile Remove="PasswordManagement\OLD-ARRAY-Config.cs" />
|
|
||||||
<Compile Remove="PasswordManagement\OLD-ARRAY-PasswordClient.cs" />
|
|
||||||
<Compile Remove="PasswordManagement\OLD-ARRAY-PasswordManager.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<Version>2.0.0-local</Version>
|
|
||||||
<Authors>PepperDash Technologies</Authors>
|
|
||||||
<Company>PepperDash Technologies</Company>
|
|
||||||
<Product>PepperDash Essentials</Product>
|
|
||||||
<Copyright>Copyright © 2025</Copyright>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageTags>Crestron; 4series</PackageTags>
|
|
||||||
<PackageOutputPath>../../output</PackageOutputPath>
|
|
||||||
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
|
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\..\LICENSE.md" Pack="true" PackagePath=""/>
|
|
||||||
<None Include="..\..\README.md" Pack="true" PackagePath=""/>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>build;</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>build;</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library'">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>build;</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Condition="$(ProjectType) == 'ProgramLibrary'">
|
|
||||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz</FileName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="$(ProjectType) == 'Program'">
|
|
||||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
|
|
||||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
|
|
||||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
|
||||||
</Delete>
|
|
||||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">
|
|
||||||
<Message Text="Creating CPLZ $(TargetDir)"></Message>
|
|
||||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))" />
|
|
||||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
|
||||||
<Copy SourceFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
|
|
||||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
|
|
||||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
|
||||||
</Delete>
|
|
||||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CPZ" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' ))">
|
|
||||||
<Message Text="Copying CPZ"></Message>
|
|
||||||
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" />
|
|
||||||
<Copy SourceFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cpz" />
|
|
||||||
</Target>
|
|
||||||
<Target Name="Copy CLZ" AfterTargets="SimplSharpPostProcess" Condition="$(ProjectType) == 'Library'">>
|
|
||||||
<Message Text="Copying CLZ"></Message>
|
|
||||||
<Move SourceFiles="$(TargetDir)\$(TargetName).clz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).clz"/>
|
|
||||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).clz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).clz"/>
|
|
||||||
</Target>
|
|
||||||
<Target Name="SimplSharpNewtonsoft" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
|
|
||||||
<ItemGroup>
|
|
||||||
<ReferencePath Condition="'%(FileName)' == 'Newtonsoft.Json.Compact'">
|
|
||||||
<Aliases>doNotUse</Aliases>
|
|
||||||
</ReferencePath>
|
|
||||||
</ItemGroup>
|
|
||||||
</Target>
|
|
||||||
</Project>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ControlSystem>
|
|
||||||
<Name>MC3 SSH</Name>
|
|
||||||
<Address>ssh 10.0.0.15</Address>
|
|
||||||
<ProgramSlot>Program01</ProgramSlot>
|
|
||||||
<Storage>Internal Flash</Storage>
|
|
||||||
</ControlSystem>
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants
|
|
||||||
/// </summary>
|
|
||||||
public class SystemInfoConstants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort BoolValueChange = 1;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort CompleteBoolChange = 2;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort BusyBoolChange = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort UshortValueChange = 101;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort StringValueChange = 201;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ConsoleResponseChange = 202;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProcessorUptimeChange = 203;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProgramUptimeChange = 204;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ObjectChange = 301;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProcessorConfigChange = 302;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort EthernetConfigChange = 303;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ControlSubnetConfigChange = 304;
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public const ushort ProgramConfigChange = 305;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processor Change Event Args Class
|
|
||||||
/// </summary>
|
|
||||||
public class ProcessorChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ProcessorInfo Processor { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ProcessorChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type)
|
|
||||||
{
|
|
||||||
Processor = processor;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
Processor = processor;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ethernet Change Event Args Class
|
|
||||||
/// </summary>
|
|
||||||
public class EthernetChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public EthernetInfo Adapter { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public EthernetChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernet"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type)
|
|
||||||
{
|
|
||||||
Adapter = ethernet;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernet"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
Adapter = ethernet;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control Subnet Chage Event Args Class
|
|
||||||
/// </summary>
|
|
||||||
public class ControlSubnetChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ControlSubnetInfo Adapter { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ControlSubnetChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type)
|
|
||||||
{
|
|
||||||
Adapter = controlSubnet;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
Adapter = controlSubnet;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Program Change Event Args Class
|
|
||||||
/// </summary>
|
|
||||||
public class ProgramChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ProgramInfo Program { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Type { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Index { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ProgramChangeEventArgs()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="program"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public ProgramChangeEventArgs(ProgramInfo program, ushort type)
|
|
||||||
{
|
|
||||||
Program = program;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor overload
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="program"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index)
|
|
||||||
{
|
|
||||||
Program = program;
|
|
||||||
Type = type;
|
|
||||||
Index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Processor info class
|
|
||||||
/// </summary>
|
|
||||||
public class ProcessorInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Model { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string SerialNumber { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Firmware { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string FirmwareDate { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string OsVersion { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string RuntimeEnvironment { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string DevicePlatform { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string ModuleDirectory { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string LocalTimeZone { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string ProgramIdTag { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ProcessorInfo()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ethernet info class
|
|
||||||
/// </summary>
|
|
||||||
public class EthernetInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort DhcpIsOn { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Hostname { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string MacAddress { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string IpAddress { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Subnet { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Gateway { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Dns1 { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Dns2 { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Dns3 { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Domain { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public EthernetInfo()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control subnet info class
|
|
||||||
/// </summary>
|
|
||||||
public class ControlSubnetInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort Enabled { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public ushort IsInAutomaticMode { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string MacAddress { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string IpAddress { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Subnet { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string RouterPrefix { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ControlSubnetInfo()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Program info class
|
|
||||||
/// </summary>
|
|
||||||
public class ProgramInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Header { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string System { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string ProgramIdTag { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string CompileTime { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Database { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Environment { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string Programmer { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public ProgramInfo()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// System Info class
|
|
||||||
/// </summary>
|
|
||||||
public class SystemInfoToSimpl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of bool change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of string change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of processor change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ProcessorChangeEventArgs> ProcessorChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of ethernet change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EthernetChangeEventArgs> EthernetChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of control subnet change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ControlSubnetChangeEventArgs> ControlSubnetChange;
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies of program change
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ProgramChangeEventArgs> ProgramChange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public SystemInfoToSimpl()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current processor info
|
|
||||||
/// </summary>
|
|
||||||
public void GetProcessorInfo()
|
|
||||||
{
|
|
||||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var processor = new ProcessorInfo();
|
|
||||||
processor.Model = InitialParametersClass.ControllerPromptName;
|
|
||||||
processor.SerialNumber = CrestronEnvironment.SystemInfo.SerialNumber;
|
|
||||||
processor.ModuleDirectory = InitialParametersClass.ProgramDirectory.ToString();
|
|
||||||
processor.ProgramIdTag = InitialParametersClass.ProgramIDTag;
|
|
||||||
processor.DevicePlatform = CrestronEnvironment.DevicePlatform.ToString();
|
|
||||||
processor.OsVersion = CrestronEnvironment.OSVersion.Version.ToString();
|
|
||||||
processor.RuntimeEnvironment = CrestronEnvironment.RuntimeEnvironment.ToString();
|
|
||||||
processor.LocalTimeZone = CrestronEnvironment.GetTimeZone().Offset;
|
|
||||||
|
|
||||||
// Does not return firmware version matching a "ver" command
|
|
||||||
// returns the "ver -v" 'CAB' version
|
|
||||||
// example return ver -v:
|
|
||||||
// RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0
|
|
||||||
// Build: 14:05:46 Oct 09 2018 (3568.25373)
|
|
||||||
// Cab: 1.503.0070
|
|
||||||
// Applications: 1.0.6855.21351
|
|
||||||
// Updater: 1.4.24
|
|
||||||
// Bootloader: 1.22.00
|
|
||||||
// RMC3-SetupProgram: 1.003.0011
|
|
||||||
// IOPVersion: FPGA [v09] slot:7
|
|
||||||
// PUF: Unknown
|
|
||||||
//Firmware = CrestronEnvironment.OSVersion.Firmware;
|
|
||||||
//Firmware = InitialParametersClass.FirmwareVersion;
|
|
||||||
|
|
||||||
// Use below logic to get actual firmware ver, not the 'CAB' returned by the above
|
|
||||||
// matches console return of a "ver" and on SystemInfo page
|
|
||||||
// example return ver:
|
|
||||||
// RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0
|
|
||||||
var response = "";
|
|
||||||
CrestronConsole.SendControlSystemCommand("ver", ref response);
|
|
||||||
processor.Firmware = ParseConsoleResponse(response, "Cntrl Eng", "[", "(");
|
|
||||||
processor.FirmwareDate = ParseConsoleResponse(response, "Cntrl Eng", "(", ")");
|
|
||||||
|
|
||||||
OnProcessorChange(processor, 0, SystemInfoConstants.ProcessorConfigChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("GetProcessorInfo failed: {0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current ethernet info
|
|
||||||
/// </summary>
|
|
||||||
public void GetEthernetInfo()
|
|
||||||
{
|
|
||||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
|
|
||||||
var adapter = new EthernetInfo();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// get lan adapter id
|
|
||||||
var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
|
|
||||||
|
|
||||||
// get lan adapter info
|
|
||||||
var dhcpState = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterId);
|
|
||||||
if (!string.IsNullOrEmpty(dhcpState))
|
|
||||||
adapter.DhcpIsOn = (ushort)(dhcpState.ToLower().Contains("on") ? 1 : 0);
|
|
||||||
|
|
||||||
adapter.Hostname = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterId);
|
|
||||||
adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId);
|
|
||||||
adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId);
|
|
||||||
adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId);
|
|
||||||
adapter.Gateway = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterId);
|
|
||||||
adapter.Domain = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterId);
|
|
||||||
|
|
||||||
// returns comma seperate list of dns servers with trailing comma
|
|
||||||
// example return: "8.8.8.8 (DHCP),8.8.4.4 (DHCP),"
|
|
||||||
string dns = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterId);
|
|
||||||
if (dns.Contains(","))
|
|
||||||
{
|
|
||||||
string[] dnsList = dns.Split(',');
|
|
||||||
for (var i = 0; i < dnsList.Length; i++)
|
|
||||||
{
|
|
||||||
if(i == 0)
|
|
||||||
adapter.Dns1 = !string.IsNullOrEmpty(dnsList[0]) ? dnsList[0] : "0.0.0.0";
|
|
||||||
if(i == 1)
|
|
||||||
adapter.Dns2 = !string.IsNullOrEmpty(dnsList[1]) ? dnsList[1] : "0.0.0.0";
|
|
||||||
if(i == 2)
|
|
||||||
adapter.Dns3 = !string.IsNullOrEmpty(dnsList[2]) ? dnsList[2] : "0.0.0.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
adapter.Dns1 = !string.IsNullOrEmpty(dns) ? dns : "0.0.0.0";
|
|
||||||
adapter.Dns2 = "0.0.0.0";
|
|
||||||
adapter.Dns3 = "0.0.0.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
OnEthernetInfoChange(adapter, 0, SystemInfoConstants.EthernetConfigChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("GetEthernetInfo failed: {0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current control subnet info
|
|
||||||
/// </summary>
|
|
||||||
public void GetControlSubnetInfo()
|
|
||||||
{
|
|
||||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
|
|
||||||
var adapter = new ControlSubnetInfo();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// get cs adapter id
|
|
||||||
var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
|
||||||
if (!adapterId.Equals(EthernetAdapterType.EthernetUnknownAdapter))
|
|
||||||
{
|
|
||||||
adapter.Enabled = 1;
|
|
||||||
adapter.IsInAutomaticMode = (ushort)(CrestronEthernetHelper.IsControlSubnetInAutomaticMode ? 1 : 0);
|
|
||||||
adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId);
|
|
||||||
adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId);
|
|
||||||
adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId);
|
|
||||||
adapter.RouterPrefix = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CONTROL_SUBNET_ROUTER_PREFIX, adapterId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
adapter.Enabled = 0;
|
|
||||||
adapter.IsInAutomaticMode = 0;
|
|
||||||
adapter.MacAddress = "NA";
|
|
||||||
adapter.IpAddress = "NA";
|
|
||||||
adapter.Subnet = "NA";
|
|
||||||
adapter.RouterPrefix = "NA";
|
|
||||||
|
|
||||||
var msg = string.Format("GetControlSubnetInfo failed: {0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnControlSubnetInfoChange(adapter, 0, SystemInfoConstants.ControlSubnetConfigChange);
|
|
||||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the program info by index
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public void GetProgramInfoByIndex(ushort index)
|
|
||||||
{
|
|
||||||
if (index < 1 || index > 10)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
|
|
||||||
var program = new ProgramInfo();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = "";
|
|
||||||
CrestronConsole.SendControlSystemCommand(string.Format("progcomments:{0}", index), ref response);
|
|
||||||
|
|
||||||
// no program loaded or running
|
|
||||||
if (response.Contains("Bad or Incomplete Command"))
|
|
||||||
{
|
|
||||||
program.Name = "";
|
|
||||||
program.System = "";
|
|
||||||
program.Programmer = "";
|
|
||||||
program.CompileTime = "";
|
|
||||||
program.Database = "";
|
|
||||||
program.Environment = "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// SIMPL returns
|
|
||||||
program.Name = ParseConsoleResponse(response, "Program File", ":", "\x0D");
|
|
||||||
program.System = ParseConsoleResponse(response, "System Name", ":", "\x0D");
|
|
||||||
program.ProgramIdTag = ParseConsoleResponse(response, "Friendly Name", ":", "\x0D");
|
|
||||||
program.Programmer = ParseConsoleResponse(response, "Programmer", ":", "\x0D");
|
|
||||||
program.CompileTime = ParseConsoleResponse(response, "Compiled On", ":", "\x0D");
|
|
||||||
program.Database = ParseConsoleResponse(response, "CrestronDB", ":", "\x0D");
|
|
||||||
program.Environment = ParseConsoleResponse(response, "Source Env", ":", "\x0D");
|
|
||||||
|
|
||||||
// S# returns
|
|
||||||
if (program.System.Length == 0)
|
|
||||||
program.System = ParseConsoleResponse(response, "Application Name", ":", "\x0D");
|
|
||||||
if (program.Database.Length == 0)
|
|
||||||
program.Database = ParseConsoleResponse(response, "PlugInVersion", ":", "\x0D");
|
|
||||||
if (program.Environment.Length == 0)
|
|
||||||
program.Environment = ParseConsoleResponse(response, "Program Tool", ":", "\x0D");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OnProgramChange(program, index, SystemInfoConstants.ProgramConfigChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("GetProgramInfoByIndex failed: {0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the processor uptime and passes it to S+
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshProcessorUptime()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string response = "";
|
|
||||||
CrestronConsole.SendControlSystemCommand("uptime", ref response);
|
|
||||||
var uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D");
|
|
||||||
OnStringChange(uptime, 0, SystemInfoConstants.ProcessorUptimeChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("RefreshProcessorUptime failed:\r{0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the program uptime, by index, and passes it to S+
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
public void RefreshProgramUptimeByIndex(int index)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string response = "";
|
|
||||||
CrestronConsole.SendControlSystemCommand(string.Format("proguptime:{0}", index), ref response);
|
|
||||||
string uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D");
|
|
||||||
OnStringChange(uptime, (ushort)index, SystemInfoConstants.ProgramUptimeChange);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("RefreshProgramUptimebyIndex({0}) failed:\r{1}", index, e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends command to console, passes response back using string change event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cmd"></param>
|
|
||||||
public void SendConsoleCommand(string cmd)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(cmd))
|
|
||||||
return;
|
|
||||||
|
|
||||||
string response = "";
|
|
||||||
CrestronConsole.SendControlSystemCommand(cmd, ref response);
|
|
||||||
if (!string.IsNullOrEmpty(response))
|
|
||||||
{
|
|
||||||
if (response.EndsWith("\x0D\\x0A"))
|
|
||||||
response.Trim('\n');
|
|
||||||
|
|
||||||
OnStringChange(response, 0, SystemInfoConstants.ConsoleResponseChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// private method to parse console messages
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data"></param>
|
|
||||||
/// <param name="line"></param>
|
|
||||||
/// <param name="dataStart"></param>
|
|
||||||
/// <param name="dataEnd"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd)
|
|
||||||
{
|
|
||||||
var response = "";
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(data) || string.IsNullOrEmpty(line) || string.IsNullOrEmpty(dataStart) || string.IsNullOrEmpty(dataEnd))
|
|
||||||
return response;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var linePos = data.IndexOf(line);
|
|
||||||
var startPos = data.IndexOf(dataStart, linePos) + dataStart.Length;
|
|
||||||
var endPos = data.IndexOf(dataEnd, startPos);
|
|
||||||
response = data.Substring(startPos, endPos - startPos).Trim();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var msg = string.Format("ParseConsoleResponse failed: {0}", e.Message);
|
|
||||||
CrestronConsole.PrintLine(msg);
|
|
||||||
//ErrorLog.Error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
var args = new BoolChangeEventArgs(state, type);
|
|
||||||
args.Index = index;
|
|
||||||
BoolChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected string change event handler
|
|
||||||
/// </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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Protected processor config change event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="processor"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnProcessorChange(ProcessorInfo processor, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = ProcessorChange;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new ProcessorChangeEventArgs(processor, type);
|
|
||||||
args.Index = index;
|
|
||||||
ProcessorChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ethernet change event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernet"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnEthernetInfoChange(EthernetInfo ethernet, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = EthernetChange;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new EthernetChangeEventArgs(ethernet, type);
|
|
||||||
args.Index = index;
|
|
||||||
EthernetChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Control Subnet change event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ethernet"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnControlSubnetInfoChange(ControlSubnetInfo ethernet, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = ControlSubnetChange;
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new ControlSubnetChangeEventArgs(ethernet, type);
|
|
||||||
args.Index = index;
|
|
||||||
ControlSubnetChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Program change event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="program"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
protected void OnProgramChange(ProgramInfo program, ushort index, ushort type)
|
|
||||||
{
|
|
||||||
var handler = ProgramChange;
|
|
||||||
|
|
||||||
if (handler != null)
|
|
||||||
{
|
|
||||||
var args = new ProgramChangeEventArgs(program, type);
|
|
||||||
args.Index = index;
|
|
||||||
ProgramChange(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Org.BouncyCastle.Asn1;
|
|
||||||
using Org.BouncyCastle.Asn1.X509;
|
|
||||||
using Org.BouncyCastle.Crypto;
|
|
||||||
using Org.BouncyCastle.Crypto.Generators;
|
|
||||||
using Org.BouncyCastle.Crypto.Prng;
|
|
||||||
using Org.BouncyCastle.Pkcs;
|
|
||||||
using Org.BouncyCastle.Security;
|
|
||||||
using Org.BouncyCastle.Utilities;
|
|
||||||
using Org.BouncyCastle.X509;
|
|
||||||
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
|
||||||
using X509KeyStorageFlags = System.Security.Cryptography.X509Certificates.X509KeyStorageFlags;
|
|
||||||
using X509ContentType = System.Security.Cryptography.X509Certificates.X509ContentType;
|
|
||||||
using Org.BouncyCastle.Crypto.Operators;
|
|
||||||
using BigInteger = Org.BouncyCastle.Math.BigInteger;
|
|
||||||
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
|
|
||||||
/// </summary>
|
|
||||||
internal class BouncyCertificate
|
|
||||||
{
|
|
||||||
public string CertificatePassword { get; set; } = "password";
|
|
||||||
public X509Certificate2 LoadCertificate(string issuerFileName, string password)
|
|
||||||
{
|
|
||||||
// We need to pass 'Exportable', otherwise we can't get the private key.
|
|
||||||
var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable);
|
|
||||||
return issuerCertificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
|
||||||
{
|
|
||||||
// It's self-signed, so these are the same.
|
|
||||||
var issuerName = issuerCertificate.Subject;
|
|
||||||
|
|
||||||
var random = GetSecureRandom();
|
|
||||||
var subjectKeyPair = GenerateKeyPair(random, 2048);
|
|
||||||
|
|
||||||
var issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey);
|
|
||||||
|
|
||||||
var serialNumber = GenerateSerialNumber(random);
|
|
||||||
var issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber());
|
|
||||||
|
|
||||||
const bool isCertificateAuthority = false;
|
|
||||||
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
|
|
||||||
subjectAlternativeNames, issuerName, issuerKeyPair,
|
|
||||||
issuerSerialNumber, isCertificateAuthority,
|
|
||||||
usages);
|
|
||||||
return ConvertCertificate(certificate, subjectKeyPair, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
|
||||||
{
|
|
||||||
// It's self-signed, so these are the same.
|
|
||||||
var issuerName = subjectName;
|
|
||||||
|
|
||||||
var random = GetSecureRandom();
|
|
||||||
var subjectKeyPair = GenerateKeyPair(random, 2048);
|
|
||||||
|
|
||||||
// It's self-signed, so these are the same.
|
|
||||||
var issuerKeyPair = subjectKeyPair;
|
|
||||||
|
|
||||||
var serialNumber = GenerateSerialNumber(random);
|
|
||||||
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
|
|
||||||
|
|
||||||
const bool isCertificateAuthority = true;
|
|
||||||
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
|
|
||||||
subjectAlternativeNames, issuerName, issuerKeyPair,
|
|
||||||
issuerSerialNumber, isCertificateAuthority,
|
|
||||||
usages);
|
|
||||||
return ConvertCertificate(certificate, subjectKeyPair, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
|
||||||
{
|
|
||||||
// It's self-signed, so these are the same.
|
|
||||||
var issuerName = subjectName;
|
|
||||||
|
|
||||||
var random = GetSecureRandom();
|
|
||||||
var subjectKeyPair = GenerateKeyPair(random, 2048);
|
|
||||||
|
|
||||||
// It's self-signed, so these are the same.
|
|
||||||
var issuerKeyPair = subjectKeyPair;
|
|
||||||
|
|
||||||
var serialNumber = GenerateSerialNumber(random);
|
|
||||||
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
|
|
||||||
|
|
||||||
const bool isCertificateAuthority = false;
|
|
||||||
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
|
|
||||||
subjectAlternativeNames, issuerName, issuerKeyPair,
|
|
||||||
issuerSerialNumber, isCertificateAuthority,
|
|
||||||
usages);
|
|
||||||
return ConvertCertificate(certificate, subjectKeyPair, random);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecureRandom GetSecureRandom()
|
|
||||||
{
|
|
||||||
// Since we're on Windows, we'll use the CryptoAPI one (on the assumption
|
|
||||||
// that it might have access to better sources of entropy than the built-in
|
|
||||||
// Bouncy Castle ones):
|
|
||||||
var randomGenerator = new CryptoApiRandomGenerator();
|
|
||||||
var random = new SecureRandom(randomGenerator);
|
|
||||||
return random;
|
|
||||||
}
|
|
||||||
|
|
||||||
private X509Certificate GenerateCertificate(SecureRandom random,
|
|
||||||
string subjectName,
|
|
||||||
AsymmetricCipherKeyPair subjectKeyPair,
|
|
||||||
BigInteger subjectSerialNumber,
|
|
||||||
string[] subjectAlternativeNames,
|
|
||||||
string issuerName,
|
|
||||||
AsymmetricCipherKeyPair issuerKeyPair,
|
|
||||||
BigInteger issuerSerialNumber,
|
|
||||||
bool isCertificateAuthority,
|
|
||||||
KeyPurposeID[] usages)
|
|
||||||
{
|
|
||||||
var certificateGenerator = new X509V3CertificateGenerator();
|
|
||||||
|
|
||||||
certificateGenerator.SetSerialNumber(subjectSerialNumber);
|
|
||||||
|
|
||||||
var issuerDN = new X509Name(issuerName);
|
|
||||||
certificateGenerator.SetIssuerDN(issuerDN);
|
|
||||||
|
|
||||||
// Note: The subject can be omitted if you specify a subject alternative name (SAN).
|
|
||||||
var subjectDN = new X509Name(subjectName);
|
|
||||||
certificateGenerator.SetSubjectDN(subjectDN);
|
|
||||||
|
|
||||||
// Our certificate needs valid from/to values.
|
|
||||||
var notBefore = DateTime.UtcNow.Date;
|
|
||||||
var notAfter = notBefore.AddYears(2);
|
|
||||||
|
|
||||||
certificateGenerator.SetNotBefore(notBefore);
|
|
||||||
certificateGenerator.SetNotAfter(notAfter);
|
|
||||||
|
|
||||||
// The subject's public key goes in the certificate.
|
|
||||||
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
|
|
||||||
|
|
||||||
AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber);
|
|
||||||
AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair);
|
|
||||||
//AddBasicConstraints(certificateGenerator, isCertificateAuthority);
|
|
||||||
|
|
||||||
if (usages != null && usages.Any())
|
|
||||||
AddExtendedKeyUsage(certificateGenerator, usages);
|
|
||||||
|
|
||||||
if (subjectAlternativeNames != null && subjectAlternativeNames.Any())
|
|
||||||
AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames);
|
|
||||||
|
|
||||||
// Set the signature algorithm. This is used to generate the thumbprint which is then signed
|
|
||||||
// with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong.
|
|
||||||
const string signatureAlgorithm = "SHA256WithRSA";
|
|
||||||
|
|
||||||
// The certificate is signed with the issuer's private key.
|
|
||||||
ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random);
|
|
||||||
var certificate = certificateGenerator.Generate(signatureFactory);
|
|
||||||
return certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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
|
|
||||||
{
|
|
||||||
var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword);
|
|
||||||
File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message));
|
|
||||||
}
|
|
||||||
// Create Base 64 encoded CER (public key only)
|
|
||||||
using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
|
|
||||||
writer.Write(contents);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\r\n{0}", ex.Message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
|
|
||||||
{
|
|
||||||
bool bRet = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
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,17 +0,0 @@
|
|||||||
using Crestron.SimplSharp.WebScripting;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Web API default request handler
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultRequestHandler : WebApiBaseRequestHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public DefaultRequestHandler()
|
|
||||||
: base(true)
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
using Crestron.SimplSharp.WebScripting;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
|
||||||
{
|
|
||||||
public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
|
|
||||||
protected readonly bool EnableCors;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestAsyncHandler(bool enableCors)
|
|
||||||
{
|
|
||||||
EnableCors = enableCors;
|
|
||||||
|
|
||||||
_handlers = new Dictionary<string, Func<HttpCwsContext, Task>>
|
|
||||||
{
|
|
||||||
{"CONNECT", HandleConnect},
|
|
||||||
{"DELETE", HandleDelete},
|
|
||||||
{"GET", HandleGet},
|
|
||||||
{"HEAD", HandleHead},
|
|
||||||
{"OPTIONS", HandleOptions},
|
|
||||||
{"PATCH", HandlePatch},
|
|
||||||
{"POST", HandlePost},
|
|
||||||
{"PUT", HandlePut},
|
|
||||||
{"TRACE", HandleTrace}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestAsyncHandler()
|
|
||||||
: this(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp.WebScripting;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// CWS Base Handler, implements IHttpCwsHandler
|
|
||||||
/// </summary>
|
|
||||||
public abstract class WebApiBaseRequestHandler : IHttpCwsHandler
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, Action<HttpCwsContext>> _handlers;
|
|
||||||
protected readonly bool EnableCors;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestHandler(bool enableCors)
|
|
||||||
{
|
|
||||||
EnableCors = enableCors;
|
|
||||||
|
|
||||||
_handlers = new Dictionary<string, Action<HttpCwsContext>>
|
|
||||||
{
|
|
||||||
{"CONNECT", HandleConnect},
|
|
||||||
{"DELETE", HandleDelete},
|
|
||||||
{"GET", HandleGet},
|
|
||||||
{"HEAD", HandleHead},
|
|
||||||
{"OPTIONS", HandleOptions},
|
|
||||||
{"PATCH", HandlePatch},
|
|
||||||
{"POST", HandlePost},
|
|
||||||
{"PUT", HandlePut},
|
|
||||||
{"TRACE", HandleTrace}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
protected WebApiBaseRequestHandler()
|
|
||||||
: this(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles CONNECT method requests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected virtual void 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 void 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 void 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 void 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 void 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 void 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 void 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 void 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 void 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)
|
|
||||||
{
|
|
||||||
Action<HttpCwsContext> handler;
|
|
||||||
|
|
||||||
if (!_handlers.TryGetValue(context.Request.HttpMethod, out handler))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EnableCors)
|
|
||||||
{
|
|
||||||
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
|
|
||||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.WebScripting;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Core.Web.RequestHandlers;
|
|
||||||
|
|
||||||
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 const uint DebugTrace = 0;
|
|
||||||
private const uint DebugInfo = 1;
|
|
||||||
private const uint DebugVerbose = 2;
|
|
||||||
|
|
||||||
private readonly CCriticalSection _serverLock = new CCriticalSection();
|
|
||||||
private HttpCwsServer _server;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Web API server key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Web API server name
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CWS base path, will default to "/api" if not set via initialize method
|
|
||||||
/// </summary>
|
|
||||||
public string BasePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates CWS is registered with base path
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRegistered { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Http request handler
|
|
||||||
/// </summary>
|
|
||||||
//public IHttpCwsHandler HttpRequestHandler
|
|
||||||
//{
|
|
||||||
// get { return _server.HttpRequestHandler; }
|
|
||||||
// set
|
|
||||||
// {
|
|
||||||
// if (_server == null) return;
|
|
||||||
// _server.HttpRequestHandler = value;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Received request event handler
|
|
||||||
/// </summary>
|
|
||||||
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
|
|
||||||
//{
|
|
||||||
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
|
|
||||||
// remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for S+. Make sure to set necessary properties using init method
|
|
||||||
/// </summary>
|
|
||||||
public WebApiServer()
|
|
||||||
: this(SplusKey, DefaultName, null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="basePath"></param>
|
|
||||||
public WebApiServer(string key, string basePath)
|
|
||||||
: this(key, DefaultName, basePath)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
if (_server == null) _server = new HttpCwsServer(BasePath);
|
|
||||||
|
|
||||||
_server.setProcessName(Key);
|
|
||||||
_server.HttpRequestHandler = new DefaultRequestHandler();
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
|
||||||
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Program status event handler
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="programEventType"></param>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
|
||||||
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ethernet event handler
|
|
||||||
/// </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 && IsRegistered)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
|
|
||||||
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes CWS class
|
|
||||||
/// </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)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_server.Routes.Add(route);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a route from CWS
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="route"></param>
|
|
||||||
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>
|
|
||||||
/// Returns a list of the current routes
|
|
||||||
/// </summary>
|
|
||||||
public HttpCwsRouteCollection GetRouteCollection()
|
|
||||||
{
|
|
||||||
return _server.Routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts CWS instance
|
|
||||||
/// </summary>
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsRegistered)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Server has already been started");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsRegistered = _server.Register();
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop CWS instance
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsRegistered = _server.Unregister() == false;
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
|
||||||
|
|
||||||
_server.Dispose();
|
|
||||||
_server = null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "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
|
|
||||||
{
|
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Received request handler
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is here for development and testing
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
|
||||||
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
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>
|
|
||||||
/// User ID
|
|
||||||
/// </summary>
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Room Type ID
|
|
||||||
/// </summary>
|
|
||||||
public int RoomTypeId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Preset Name
|
|
||||||
/// </summary>
|
|
||||||
public string PresetName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Preset Number
|
|
||||||
/// </summary>
|
|
||||||
public int PresetNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Preset Data
|
|
||||||
/// </summary>
|
|
||||||
public string Data { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
|
||||||
public Preset()
|
|
||||||
{
|
|
||||||
PresetName = "";
|
|
||||||
PresetNumber = 1;
|
|
||||||
Data = "{}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class PresetReceivedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// True when the preset is found
|
|
||||||
/// </summary>
|
|
||||||
public bool LookupSuccess { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// S+ helper
|
|
||||||
/// </summary>
|
|
||||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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>
|
|
||||||
/// For stupid S+
|
|
||||||
/// </summary>
|
|
||||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public class UserAndRoomMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int RoomTypeId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
public int PresetNumber { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +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>
|
|
||||||
/// Unique identifier for this instance
|
|
||||||
/// </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>
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
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 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Serialization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface to determine XSig serialization for an object.
|
|
||||||
/// </summary>
|
|
||||||
public interface IXSigSerialization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize the sig data
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
IEnumerable<XSigToken> Serialize();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize the sig data
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="tokens"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Serialization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class to handle this specific exception type
|
|
||||||
/// </summary>
|
|
||||||
public class XSigSerializationException : Exception
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// default constructor
|
|
||||||
/// </summary>
|
|
||||||
public XSigSerializationException() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// constructor with message
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message"></param>
|
|
||||||
public XSigSerializationException(string message) : base(message) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// constructor with message and innner exception
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message"></param>
|
|
||||||
/// <param name="inner"></param>
|
|
||||||
public XSigSerializationException(string message, Exception inner) : base(message, inner) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an XSigAnalogToken
|
|
||||||
/// </summary>
|
|
||||||
public sealed class XSigAnalogToken : XSigToken, IFormattable
|
|
||||||
{
|
|
||||||
private readonly ushort _value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
public override XSigTokenType TokenType
|
|
||||||
{
|
|
||||||
get { return XSigTokenType.Analog; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override byte[] GetBytes()
|
|
||||||
{
|
|
||||||
return new[] {
|
|
||||||
(byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index - 1 >> 7)),
|
|
||||||
(byte)((Index - 1) & 0x7F),
|
|
||||||
(byte)((Value & 0x3F80) >> 7),
|
|
||||||
(byte)(Value & 0x7F)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override XSigToken GetTokenWithOffset(int offset)
|
|
||||||
{
|
|
||||||
if (offset == 0) return this;
|
|
||||||
return new XSigAnalogToken(Index + offset, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Index + " = 0x" + Value.ToString("X4");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"></param>
|
|
||||||
/// <param name="formatProvider"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string ToString(string format, IFormatProvider formatProvider)
|
|
||||||
{
|
|
||||||
return Value.ToString(format, formatProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an XSigDigitalToken
|
|
||||||
/// </summary>
|
|
||||||
public sealed class XSigDigitalToken : XSigToken
|
|
||||||
{
|
|
||||||
private readonly bool _value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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>
|
|
||||||
public override XSigTokenType TokenType
|
|
||||||
{
|
|
||||||
get { return XSigTokenType.Digital; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override byte[] GetBytes()
|
|
||||||
{
|
|
||||||
return new[] {
|
|
||||||
(byte)(0x80 | (Value ? 0 : 0x20) | ((Index - 1) >> 7)),
|
|
||||||
(byte)((Index - 1) & 0x7F)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override XSigToken GetTokenWithOffset(int offset)
|
|
||||||
{
|
|
||||||
if (offset == 0) return this;
|
|
||||||
return new XSigDigitalToken(Index + offset, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Index + " = " + (Value ? "High" : "Low");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formatProvider"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string ToString(IFormatProvider formatProvider)
|
|
||||||
{
|
|
||||||
return Value.ToString(formatProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Tokens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an XSigSerialToken
|
|
||||||
/// </summary>
|
|
||||||
public sealed class XSigSerialToken : XSigToken
|
|
||||||
{
|
|
||||||
private readonly string _value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
public override XSigTokenType TokenType
|
|
||||||
{
|
|
||||||
get { return XSigTokenType.Serial; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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);
|
|
||||||
return xsig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </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>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Index + " = \"" + Value + "\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
namespace PepperDash.Core.Intersystem.Tokens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the base class for all XSig datatypes.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class XSigToken
|
|
||||||
{
|
|
||||||
private readonly int _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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
namespace PepperDash.Core.Intersystem.Tokens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// XSig token types.
|
|
||||||
/// </summary>
|
|
||||||
public enum XSigTokenType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Digital signal datatype.
|
|
||||||
/// </summary>
|
|
||||||
Digital,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Analog signal datatype.
|
|
||||||
/// </summary>
|
|
||||||
Analog,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serial signal datatype.
|
|
||||||
/// </summary>
|
|
||||||
Serial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using PepperDash.Core.Intersystem.Serialization;
|
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Digital (2 bytes)
|
|
||||||
10C##### 0####### (mask = 11000000_10000000b -> 0xC080)
|
|
||||||
|
|
||||||
Analog (4 bytes)
|
|
||||||
11aa0### 0####### (mask = 11001000_10000000b -> 0xC880)
|
|
||||||
0aaaaaaa 0aaaaaaa
|
|
||||||
|
|
||||||
Serial (Variable length)
|
|
||||||
11001### 0####### (mask = 11111000_10000000b -> 0xF880)
|
|
||||||
dddddddd ........ <- up to 252 bytes of serial data (255 - 3)
|
|
||||||
11111111 <- denotes end of data
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper methods for creating XSig byte sequences compatible with the Intersystem Communications (ISC) symbol.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Indexing is not from the start of each signal type but rather from the beginning of the first defined signal
|
|
||||||
/// the Intersystem Communications (ISC) symbol.
|
|
||||||
/// </remarks>
|
|
||||||
public static class XSigHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Forces all outputs to 0.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Bytes in XSig format for clear outputs trigger.</returns>
|
|
||||||
public static byte[] ClearOutputs()
|
|
||||||
{
|
|
||||||
return new byte[] { 0xFC };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluate all inputs and re-transmit any digital, analog, and permanent serail signals not set to 0.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Bytes in XSig format for send status trigger.</returns>
|
|
||||||
public static byte[] SendStatus()
|
|
||||||
{
|
|
||||||
return new byte[] { 0xFD };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for an IXSigStateResolver object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xSigSerialization">XSig state resolver.</param>
|
|
||||||
/// <returns>Bytes in XSig format for each token within the state representation.</returns>
|
|
||||||
public static byte[] GetBytes(IXSigSerialization xSigSerialization)
|
|
||||||
{
|
|
||||||
return GetBytes(xSigSerialization, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for an IXSigStateResolver object, with a specified offset.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xSigSerialization">XSig state resolver.</param>
|
|
||||||
/// <param name="offset">Offset to which the data will be aligned.</param>
|
|
||||||
/// <returns>Bytes in XSig format for each token within the state representation.</returns>
|
|
||||||
public static byte[] GetBytes(IXSigSerialization xSigSerialization, int offset)
|
|
||||||
{
|
|
||||||
var tokens = xSigSerialization.Serialize();
|
|
||||||
if (tokens == null) return new byte[0];
|
|
||||||
using (var memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
using (var tokenWriter = new XSigTokenStreamWriter(memoryStream))
|
|
||||||
tokenWriter.WriteXSigData(xSigSerialization, offset);
|
|
||||||
|
|
||||||
return memoryStream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single digital signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based digital index</param>
|
|
||||||
/// <param name="value">Digital data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for digtial information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, bool value)
|
|
||||||
{
|
|
||||||
return GetBytes(index, 0, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single digital signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based digital index</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="value">Digital data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for digtial information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, int offset, bool value)
|
|
||||||
{
|
|
||||||
return new XSigDigitalToken(index + offset, value).GetBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple digital signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="values">Digital signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for digital signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, bool[] values)
|
|
||||||
{
|
|
||||||
return GetBytes(startIndex, 0, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple digital signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="values">Digital signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for digital signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, int offset, bool[] values)
|
|
||||||
{
|
|
||||||
// Digital XSig data is 2 bytes per value
|
|
||||||
const int fixedLength = 2;
|
|
||||||
var bytes = new byte[values.Length * fixedLength];
|
|
||||||
for (var i = 0; i < values.Length; i++)
|
|
||||||
Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength);
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single analog signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based analog index</param>
|
|
||||||
/// <param name="value">Analog data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for analog signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, ushort value)
|
|
||||||
{
|
|
||||||
return GetBytes(index, 0, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single analog signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based analog index</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="value">Analog data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for analog signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, int offset, ushort value)
|
|
||||||
{
|
|
||||||
return new XSigAnalogToken(index + offset, value).GetBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple analog signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="values">Analog signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for analog signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, ushort[] values)
|
|
||||||
{
|
|
||||||
return GetBytes(startIndex, 0, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple analog signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="values">Analog signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for analog signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, int offset, ushort[] values)
|
|
||||||
{
|
|
||||||
// Analog XSig data is 4 bytes per value
|
|
||||||
const int fixedLength = 4;
|
|
||||||
var bytes = new byte[values.Length * fixedLength];
|
|
||||||
for (var i = 0; i < values.Length; i++)
|
|
||||||
Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength);
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single serial signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based serial index</param>
|
|
||||||
/// <param name="value">Serial data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for serial signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, string value)
|
|
||||||
{
|
|
||||||
return GetBytes(index, 0, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get bytes for a single serial signal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">1-based serial index</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="value">Serial data to be encoded</param>
|
|
||||||
/// <returns>Bytes in XSig format for serial signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int index, int offset, string value)
|
|
||||||
{
|
|
||||||
return new XSigSerialToken(index + offset, value).GetBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple serial signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="values">Serial signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for serial signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, string[] values)
|
|
||||||
{
|
|
||||||
return GetBytes(startIndex, 0, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get byte sequence for multiple serial signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startIndex">Starting index of the sequence.</param>
|
|
||||||
/// <param name="offset">Index offset.</param>
|
|
||||||
/// <param name="values">Serial signal value array.</param>
|
|
||||||
/// <returns>Byte sequence in XSig format for serial signal information.</returns>
|
|
||||||
public static byte[] GetBytes(int startIndex, int offset, string[] values)
|
|
||||||
{
|
|
||||||
// Serial XSig data is not fixed-length like the other formats
|
|
||||||
var dstOffset = 0;
|
|
||||||
var bytes = new byte[values.Sum(v => v.Length + 3)];
|
|
||||||
for (var i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
var data = GetBytes(startIndex++, offset, values[i]);
|
|
||||||
Buffer.BlockCopy(data, 0, bytes, dstOffset, data.Length);
|
|
||||||
dstOffset += data.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using PepperDash.Core.Intersystem.Serialization;
|
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream reader.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class XSigTokenStreamReader : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Stream _stream;
|
|
||||||
private readonly bool _leaveOpen;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream reader constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Input stream to read from.</param>
|
|
||||||
/// <exception cref="T:System.ArgumentNullException">Stream is null.</exception>
|
|
||||||
/// <exception cref="T:System.ArgumentException">Stream cannot be read from.</exception>
|
|
||||||
public XSigTokenStreamReader(Stream stream)
|
|
||||||
: this(stream, false) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream reader constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Input stream to read from.</param>
|
|
||||||
/// <param name="leaveOpen">Determines whether to leave the stream open or not.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Stream is null.</exception>
|
|
||||||
/// <exception cref="ArgumentException">Stream cannot be read from.</exception>
|
|
||||||
public XSigTokenStreamReader(Stream stream, bool leaveOpen)
|
|
||||||
{
|
|
||||||
if (stream == null)
|
|
||||||
throw new ArgumentNullException("stream");
|
|
||||||
if (!stream.CanRead)
|
|
||||||
throw new ArgumentException("The specified stream cannot be read from.");
|
|
||||||
|
|
||||||
_stream = stream;
|
|
||||||
_leaveOpen = leaveOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads a 16-bit unsigned integer from the specified stream using Big Endian byte order.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Input stream</param>
|
|
||||||
/// <param name="value">Result</param>
|
|
||||||
/// <returns>True if successful, otherwise false.</returns>
|
|
||||||
public static bool TryReadUInt16BE(Stream stream, out ushort value)
|
|
||||||
{
|
|
||||||
value = 0;
|
|
||||||
if (stream.Length < 2)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var buffer = new byte[2];
|
|
||||||
stream.Read(buffer, 0, 2);
|
|
||||||
value = (ushort)((buffer[0] << 8) | buffer[1]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read XSig token from the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>XSigToken</returns>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Offset is less than 0.</exception>
|
|
||||||
public XSigToken ReadXSigToken()
|
|
||||||
{
|
|
||||||
ushort prefix;
|
|
||||||
if (!TryReadUInt16BE(_stream, out prefix))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if ((prefix & 0xF880) == 0xC800) // Serial data
|
|
||||||
{
|
|
||||||
var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F);
|
|
||||||
var n = 0;
|
|
||||||
const int maxSerialDataLength = 252;
|
|
||||||
var chars = new char[maxSerialDataLength];
|
|
||||||
int ch;
|
|
||||||
while ((ch = _stream.ReadByte()) != 0xFF)
|
|
||||||
{
|
|
||||||
if (ch == -1) // Reached end of stream without end of data marker
|
|
||||||
return null;
|
|
||||||
|
|
||||||
chars[n++] = (char)ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new XSigSerialToken((ushort)(index + 1), new string(chars, 0, n));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((prefix & 0xC880) == 0xC000) // Analog data
|
|
||||||
{
|
|
||||||
ushort data;
|
|
||||||
if (!TryReadUInt16BE(_stream, out data))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F);
|
|
||||||
var value = ((prefix & 0x3000) << 2) | ((data & 0x7F00) >> 1) | (data & 0x7F);
|
|
||||||
return new XSigAnalogToken((ushort)(index + 1), (ushort)value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((prefix & 0xC080) == 0x8000) // Digital data
|
|
||||||
{
|
|
||||||
var index = ((prefix & 0x1F00) >> 1) | (prefix & 0x7F);
|
|
||||||
var value = (prefix & 0x2000) == 0;
|
|
||||||
return new XSigDigitalToken((ushort)(index + 1), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads all available XSig tokens from the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>XSigToken collection.</returns>
|
|
||||||
public IEnumerable<XSigToken> ReadAllXSigTokens()
|
|
||||||
{
|
|
||||||
var tokens = new List<XSigToken>();
|
|
||||||
XSigToken token;
|
|
||||||
while ((token = ReadXSigToken()) != null)
|
|
||||||
tokens.Add(token);
|
|
||||||
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to deserialize all XSig data within the stream from the current position.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type to deserialize the information to.</typeparam>
|
|
||||||
/// <returns>Deserialized object.</returns>
|
|
||||||
public T DeserializeStream<T>()
|
|
||||||
where T : class, IXSigSerialization, new()
|
|
||||||
{
|
|
||||||
return new T().Deserialize<T>(ReadAllXSigTokens());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes of the internal stream if specified to not leave open.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!_leaveOpen)
|
|
||||||
_stream.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
|
||||||
using PepperDash.Core.Intersystem.Serialization;
|
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream writer.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class XSigTokenStreamWriter : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Stream _stream;
|
|
||||||
private readonly bool _leaveOpen;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream writer constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Input stream to write to.</param>
|
|
||||||
/// <exception cref="T:System.ArgumentNullException">Stream is null.</exception>
|
|
||||||
/// <exception cref="T:System.ArgumentException">Stream cannot be written to.</exception>
|
|
||||||
public XSigTokenStreamWriter(Stream stream)
|
|
||||||
: this(stream, false) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// XSigToken stream writer constructor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Input stream to write to.</param>
|
|
||||||
/// <param name="leaveOpen">Determines whether to leave the stream open or not.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Stream is null.</exception>
|
|
||||||
/// <exception cref="ArgumentException">Stream cannot be written to.</exception>
|
|
||||||
public XSigTokenStreamWriter(Stream stream, bool leaveOpen)
|
|
||||||
{
|
|
||||||
if (stream == null)
|
|
||||||
throw new ArgumentNullException("stream");
|
|
||||||
if (!stream.CanWrite)
|
|
||||||
throw new ArgumentException("The specified stream cannot be written to.");
|
|
||||||
|
|
||||||
_stream = stream;
|
|
||||||
_leaveOpen = leaveOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write XSig data gathered from an IXSigStateResolver to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xSigSerialization">IXSigStateResolver object.</param>
|
|
||||||
public void WriteXSigData(IXSigSerialization xSigSerialization)
|
|
||||||
{
|
|
||||||
WriteXSigData(xSigSerialization, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write XSig data gathered from an IXSigStateResolver to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="xSigSerialization">IXSigStateResolver object.</param>
|
|
||||||
/// <param name="offset">Index offset for each XSigToken.</param>
|
|
||||||
public void WriteXSigData(IXSigSerialization xSigSerialization, int offset)
|
|
||||||
{
|
|
||||||
if (xSigSerialization == null)
|
|
||||||
throw new ArgumentNullException("xSigSerialization");
|
|
||||||
|
|
||||||
var tokens = xSigSerialization.Serialize();
|
|
||||||
WriteXSigData(tokens, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write XSigToken to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="token">XSigToken object.</param>
|
|
||||||
public void WriteXSigData(XSigToken token)
|
|
||||||
{
|
|
||||||
WriteXSigData(token, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write XSigToken to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="token">XSigToken object.</param>
|
|
||||||
/// <param name="offset">Index offset for each XSigToken.</param>
|
|
||||||
public void WriteXSigData(XSigToken token, int offset)
|
|
||||||
{
|
|
||||||
WriteXSigData(new[] { token }, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes an array of XSigTokens to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tokens">XSigToken objects.</param>
|
|
||||||
public void WriteXSigData(XSigToken[] tokens)
|
|
||||||
{
|
|
||||||
WriteXSigData(tokens.AsEnumerable());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write an enumerable collection of XSigTokens to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tokens">XSigToken objects.</param>
|
|
||||||
public void WriteXSigData(IEnumerable<XSigToken> tokens)
|
|
||||||
{
|
|
||||||
WriteXSigData(tokens, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write an enumerable collection of XSigTokens to the stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tokens">XSigToken objects.</param>
|
|
||||||
/// <param name="offset">Index offset for each XSigToken.</param>
|
|
||||||
public void WriteXSigData(IEnumerable<XSigToken> tokens, int offset)
|
|
||||||
{
|
|
||||||
if (offset < 0)
|
|
||||||
throw new ArgumentOutOfRangeException("offset", "Offset must be greater than or equal to 0.");
|
|
||||||
|
|
||||||
if (tokens != null)
|
|
||||||
{
|
|
||||||
foreach (var token in tokens)
|
|
||||||
{
|
|
||||||
if (token == null) continue;
|
|
||||||
var bytes = token.GetTokenWithOffset(offset).GetBytes();
|
|
||||||
_stream.Write(bytes, 0, bytes.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes of the internal stream if specified to not leave open.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!_leaveOpen)
|
|
||||||
_stream.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Bridges
|
namespace PepperDash.Essentials.Core.Bridges
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Bridges
|
namespace PepperDash.Essentials.Core.Bridges
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ namespace PepperDash.Essentials.Core
|
|||||||
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
|
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
|
||||||
public class GenericHttpClient : Device, IBasicCommunication
|
public class GenericHttpClient : Device, IBasicCommunication
|
||||||
{
|
{
|
||||||
private readonly HttpClient Client;
|
public HttpClient Client;
|
||||||
public event EventHandler<GenericHttpClientEventArgs> ResponseRecived;
|
public event EventHandler<GenericHttpClientEventArgs> ResponseRecived;
|
||||||
|
|
||||||
public GenericHttpClient(string key, string name, string hostname)
|
public GenericHttpClient(string key, string name, string hostname)
|
||||||
: base(key, name)
|
: base(key, name)
|
||||||
{
|
{
|
||||||
Client = new HttpClient
|
Client = new HttpClient();
|
||||||
{
|
Client.HostName = hostname;
|
||||||
HostName = hostname
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -56,7 +54,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
if (responseReceived.ContentString.Length > 0)
|
if (responseReceived.ContentString.Length > 0)
|
||||||
{
|
{
|
||||||
ResponseRecived?.Invoke(this, new GenericHttpClientEventArgs(responseReceived.ContentString, (request as HttpClientRequest).Url.ToString(), error));
|
if (ResponseRecived != null)
|
||||||
|
ResponseRecived(this, new GenericHttpClientEventArgs(responseReceived.ContentString, (request as HttpClientRequest).Url.ToString(), error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a generic digital input deviced tied to a versiport
|
/// Represents a generic digital input deviced tied to a versiport
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericVersiportDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput, IPartitionStateProvider
|
public class GenericVersiportDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput
|
||||||
{
|
{
|
||||||
public Versiport InputPort { get; private set; }
|
public Versiport InputPort { get; private set; }
|
||||||
|
|
||||||
@@ -35,15 +35,10 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoolFeedback PartitionPresentFeedback { get; }
|
|
||||||
|
|
||||||
public bool PartitionPresent => !InputStateFeedbackFunc();
|
|
||||||
|
|
||||||
public GenericVersiportDigitalInputDevice(string key, string name, Func<IOPortConfig, Versiport> postActivationFunc, IOPortConfig config) :
|
public GenericVersiportDigitalInputDevice(string key, string name, Func<IOPortConfig, Versiport> postActivationFunc, IOPortConfig config) :
|
||||||
base(key, name)
|
base(key, name)
|
||||||
{
|
{
|
||||||
InputStateFeedback = new BoolFeedback(InputStateFeedbackFunc);
|
InputStateFeedback = new BoolFeedback(InputStateFeedbackFunc);
|
||||||
PartitionPresentFeedback = new BoolFeedback(() => !InputStateFeedbackFunc());
|
|
||||||
|
|
||||||
AddPostActivationAction(() =>
|
AddPostActivationAction(() =>
|
||||||
{
|
{
|
||||||
@@ -57,8 +52,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
|||||||
|
|
||||||
InputPort.VersiportChange += InputPort_VersiportChange;
|
InputPort.VersiportChange += InputPort_VersiportChange;
|
||||||
|
|
||||||
InputStateFeedback.FireUpdate();
|
|
||||||
PartitionPresentFeedback.FireUpdate();
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Created GenericVersiportDigitalInputDevice on port '{0}'. DisablePullUpResistor: '{1}'", config.PortNumber, InputPort.DisablePullUpResistor);
|
Debug.LogMessage(LogEventLevel.Debug, this, "Created GenericVersiportDigitalInputDevice on port '{0}'. DisablePullUpResistor: '{1}'", config.PortNumber, InputPort.DisablePullUpResistor);
|
||||||
|
|
||||||
@@ -71,10 +65,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
|||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Versiport change: {0}", args.Event);
|
Debug.LogMessage(LogEventLevel.Debug, this, "Versiport change: {0}", args.Event);
|
||||||
|
|
||||||
if(args.Event == eVersiportEvent.DigitalInChange)
|
if(args.Event == eVersiportEvent.DigitalInChange)
|
||||||
{
|
|
||||||
InputStateFeedback.FireUpdate();
|
InputStateFeedback.FireUpdate();
|
||||||
PartitionPresentFeedback.FireUpdate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
using PepperDash.Core;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
public interface IDisplay: IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
public interface IEmergencyOSD
|
|
||||||
{
|
|
||||||
void ShowEmergencyMessage(string url);
|
|
||||||
void HideEmergencyMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,27 @@
|
|||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a device that has selectable inputs
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">the type to use as the key for each input item. Most likely an enum or string</typeparam>\
|
||||||
|
/// <example>
|
||||||
|
/// See MockDisplay for example implemntation
|
||||||
|
/// </example>
|
||||||
|
[Obsolete("Use IHasInputs<T> instead. Will be removed for 2.0 release")]
|
||||||
|
public interface IHasInputs<T, TSelector>: IKeyName
|
||||||
|
{
|
||||||
|
ISelectableItems<T> Inputs { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a device that has selectable inputs
|
/// Describes a device that has selectable inputs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
public interface IHasWebView
|
|
||||||
{
|
|
||||||
bool WebviewIsVisible { get; }
|
|
||||||
void ShowWebView(string url, string mode, string title, string target);
|
|
||||||
void HideWebView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
@@ -9,6 +11,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
public class DeviceJsonApi
|
public class DeviceJsonApi
|
||||||
@@ -138,26 +141,18 @@ namespace PepperDash.Essentials.Core
|
|||||||
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
|
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params);
|
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params);
|
||||||
var result = method.Invoke(obj, convertedParams);
|
method.Invoke(obj, convertedParams);
|
||||||
|
|
||||||
// If the method returns a Task, await it
|
|
||||||
if (result is Task task)
|
|
||||||
{
|
|
||||||
await task;
|
|
||||||
}
|
|
||||||
// If the method returns a Task<T>, await it
|
|
||||||
else if (result != null && result.GetType().IsGenericType && result.GetType().GetGenericTypeDefinition() == typeof(Task<>))
|
|
||||||
{
|
|
||||||
await (Task)result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
|
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
using PepperDash.Core;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharpPro;
|
||||||
|
using Crestron.SimplSharpPro.DeviceSupport;
|
||||||
|
|
||||||
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
@@ -8,6 +16,22 @@ namespace PepperDash.Essentials.Core
|
|||||||
BoolFeedback IsOnline { get; }
|
BoolFeedback IsOnline { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// ** WANT THIS AND ALL ITS FRIENDS TO GO AWAY **
|
||||||
|
///// Defines a class that has a list of CueAction objects, typically
|
||||||
|
///// for linking functions to user interfaces or API calls
|
||||||
|
///// </summary>
|
||||||
|
//public interface IHasCueActionList
|
||||||
|
//{
|
||||||
|
// List<CueActionPair> CueActionList { get; }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//public interface IHasComPortsHardware
|
||||||
|
//{
|
||||||
|
// IComPorts ComPortsDevice { get; }
|
||||||
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a device that can have a video sync providing device attached to it
|
/// Describes a device that can have a video sync providing device attached to it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
81
src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs
Normal file
81
src/PepperDash.Essentials.Core/Devices/PC/Laptop.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Crestron.SimplSharpPro;
|
||||||
|
|
||||||
|
using PepperDash.Essentials.Core;
|
||||||
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
using PepperDash.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Devices
|
||||||
|
{
|
||||||
|
|
||||||
|
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||||
|
public class Laptop : EssentialsDevice, IHasFeedback, IRoutingOutputs, IAttachVideoStatus, IUiDisplayInfo, IUsageTracking
|
||||||
|
{
|
||||||
|
public uint DisplayUiType { get { return DisplayUiConstants.TypeLaptop; } }
|
||||||
|
public string IconName { get; set; }
|
||||||
|
public BoolFeedback HasPowerOnFeedback { get; private set; }
|
||||||
|
|
||||||
|
public RoutingOutputPort AnyVideoOut { get; private set; }
|
||||||
|
|
||||||
|
#region IRoutingOutputs Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options: hdmi
|
||||||
|
/// </summary>
|
||||||
|
public RoutingPortCollection<RoutingOutputPort> OutputPorts { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Laptop(string key, string name)
|
||||||
|
: base(key, name)
|
||||||
|
{
|
||||||
|
IconName = "Laptop";
|
||||||
|
HasPowerOnFeedback = new BoolFeedback("HasPowerFeedback",
|
||||||
|
() => this.GetVideoStatuses() != VideoStatusOutputs.NoStatus);
|
||||||
|
OutputPorts = new RoutingPortCollection<RoutingOutputPort>();
|
||||||
|
OutputPorts.Add(AnyVideoOut = new RoutingOutputPort(RoutingPortNames.AnyOut, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
||||||
|
eRoutingPortConnectionType.None, 0, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IHasFeedback Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Passes through the VideoStatuses list
|
||||||
|
/// </summary>
|
||||||
|
public FeedbackCollection<Feedback> Feedbacks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var newList = new FeedbackCollection<Feedback>();
|
||||||
|
newList.AddRange(this.GetVideoStatuses().ToList());
|
||||||
|
return newList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IUsageTracking Members
|
||||||
|
|
||||||
|
public UsageTracking UsageTracker { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||||
|
public class LaptopFactory : EssentialsDeviceFactory<Laptop>
|
||||||
|
{
|
||||||
|
public LaptopFactory()
|
||||||
|
{
|
||||||
|
TypeNames = new List<string>() { "deprecated" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Laptop Device");
|
||||||
|
return new Core.Devices.Laptop(dc.Key, dc.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user