mirror of
https://github.com/PepperDash/PepperDashCore.git
synced 2026-08-31 10:58:25 +00:00
commit
ab61b2673a
105 changed files with 12503 additions and 13387 deletions
BIN
._Readme.md
BIN
._Readme.md
Binary file not shown.
45
.github/scripts/GenerateVersionNumber.ps1
vendored
45
.github/scripts/GenerateVersionNumber.ps1
vendored
|
|
@ -1,45 +0,0 @@
|
|||
$latestVersions = $(git tag --merged origin/main)
|
||||
$latestVersion = [version]"0.0.0"
|
||||
Foreach ($version in $latestVersions) {
|
||||
Write-Host $version
|
||||
try {
|
||||
if (([version]$version) -ge $latestVersion) {
|
||||
$latestVersion = $version
|
||||
Write-Host "Setting latest version to: $latestVersion"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "Unable to convert $($version). Skipping"
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$newVersion = [version]$latestVersion
|
||||
$phase = ""
|
||||
$newVersionString = ""
|
||||
switch -regex ($Env:GITHUB_REF) {
|
||||
'^refs\/heads\/main*.' {
|
||||
$newVersionString = "{0}.{1}.{2}" -f $newVersion.Major, $newVersion.Minor, $newVersion.Build
|
||||
}
|
||||
'^refs\/heads\/feature\/*.' {
|
||||
$phase = 'alpha'
|
||||
$newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/release\/*.' {
|
||||
$splitRef = $Env:GITHUB_REF -split "/"
|
||||
$version = [version]($splitRef[-1] -replace "v", "")
|
||||
$phase = 'rc'
|
||||
$newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $version.Major, $version.Minor, $version.Build, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/development*.' {
|
||||
$phase = 'beta'
|
||||
$newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/hotfix\/*.' {
|
||||
$phase = 'hotfix'
|
||||
$newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Write-Output $newVersionString
|
||||
40
.github/scripts/UpdateAssemblyVersion.ps1
vendored
40
.github/scripts/UpdateAssemblyVersion.ps1
vendored
|
|
@ -1,40 +0,0 @@
|
|||
function Update-SourceVersion {
|
||||
Param ([string]$Version)
|
||||
#$fullVersion = $Version
|
||||
$baseVersion = [regex]::Match($Version, "(\d+.\d+.\d+).*").captures.groups[1].value
|
||||
$NewAssemblyVersion = ‘AssemblyVersion("‘ + $baseVersion + ‘.*")’
|
||||
Write-Output "AssemblyVersion = $NewAssemblyVersion"
|
||||
$NewAssemblyInformationalVersion = ‘AssemblyInformationalVersion("‘ + $Version + ‘")’
|
||||
Write-Output "AssemblyInformationalVersion = $NewAssemblyInformationalVersion"
|
||||
|
||||
foreach ($o in $input) {
|
||||
Write-output $o.FullName
|
||||
$TmpFile = $o.FullName + “.tmp”
|
||||
get-content $o.FullName |
|
||||
ForEach-Object {
|
||||
$_ -replace ‘AssemblyVersion\(".*"\)’, $NewAssemblyVersion } |
|
||||
ForEach-Object {
|
||||
$_ -replace ‘AssemblyInformationalVersion\(".*"\)’, $NewAssemblyInformationalVersion
|
||||
} > $TmpFile
|
||||
move-item $TmpFile $o.FullName -force
|
||||
}
|
||||
}
|
||||
|
||||
function Update-AllAssemblyInfoFiles ( $version ) {
|
||||
foreach ($file in “AssemblyInfo.cs”, “AssemblyInfo.vb” ) {
|
||||
get-childitem -Path $Env:GITHUB_WORKSPACE -recurse | Where-Object { $_.Name -eq $file } | Update-SourceVersion $version ;
|
||||
}
|
||||
}
|
||||
|
||||
# validate arguments
|
||||
$r = [System.Text.RegularExpressions.Regex]::Match($args[0], "\d+\.\d+\.\d+.*");
|
||||
if ($r.Success) {
|
||||
Write-Output "Updating Assembly Version to $args ...";
|
||||
Update-AllAssemblyInfoFiles $args[0];
|
||||
}
|
||||
else {
|
||||
Write-Output ” “;
|
||||
Write-Output “Error: Input version does not match x.y.z format!”
|
||||
Write-Output ” “;
|
||||
Write-Output "Unable to apply version to AssemblyInfo.cs files";
|
||||
}
|
||||
42
.github/scripts/ZipBuildOutput.ps1
vendored
42
.github/scripts/ZipBuildOutput.ps1
vendored
|
|
@ -1,42 +0,0 @@
|
|||
# Uncomment these for local testing
|
||||
# $Env:GITHUB_WORKSPACE = "C:\Working Directories\PD\essentials"
|
||||
# $Env:SOLUTION_FILE = "PepperDashEssentials"
|
||||
# $Env:VERSION = "0.0.0-buildType-test"
|
||||
|
||||
# Sets the root directory for the operation
|
||||
$destination = "$($Env:GITHUB_HOME)\output"
|
||||
New-Item -ItemType Directory -Force -Path ($destination)
|
||||
Get-ChildItem ($destination)
|
||||
$exclusions = @(git submodule foreach --quiet 'echo $name')
|
||||
$exclusions += "Newtonsoft.Json.Compact.dll"
|
||||
# Trying to get any .json schema files (not currently working)
|
||||
# Gets any files with the listed extensions.
|
||||
Get-ChildItem -recurse -Path "$($Env:GITHUB_WORKSPACE)" -include "*.clz", "*.cpz", "*.cplz", "*.json", "*.nuspec" | ForEach-Object {
|
||||
$allowed = $true;
|
||||
# Exclude any files in submodules
|
||||
foreach ($exclude in $exclusions) {
|
||||
if ((Split-Path $_.FullName -Parent).contains("$($exclude)")) {
|
||||
$allowed = $false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($allowed) {
|
||||
Write-Host "allowing $($_)"
|
||||
$_;
|
||||
}
|
||||
} | Copy-Item -Destination ($destination) -Force
|
||||
Write-Host "Getting matching files..."
|
||||
# Get any files from the output folder that match the following extensions
|
||||
Get-ChildItem -Path $destination | Where-Object { (($_.Extension -eq ".clz") -or ($_.Extension -eq ".cpz") -or ($_.Extension -eq ".cplz")) } | ForEach-Object {
|
||||
# Replace the extensions with dll or xml and create an array
|
||||
$filenames = @($($_ -replace "cpz|clz|cplz", "dll"), $($_ -replace "cpz|clz|cplz", "xml"))
|
||||
Write-Host "Filenames:"
|
||||
Write-Host $filenames
|
||||
if ($filenames.length -gt 0) {
|
||||
# Attempt to get the files and return them to the output directory
|
||||
Get-ChildItem -Recurse -Path "$($Env:GITHUB_WORKSPACE)" -include $filenames | Copy-Item -Destination ($destination) -Force
|
||||
}
|
||||
}
|
||||
Compress-Archive -Path $destination -DestinationPath "$($Env:GITHUB_WORKSPACE)\$($Env:SOLUTION_FILE)-$($Env:VERSION).zip" -Force
|
||||
Write-Host "Output Contents post Zip"
|
||||
Get-ChildItem -Path $destination
|
||||
21
.github/workflows/EssentialsPlugins-builds-4-series-caller.yml
vendored
Normal file
21
.github/workflows/EssentialsPlugins-builds-4-series-caller.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: Build PepperDash Core
|
||||
|
||||
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 }}
|
||||
154
.github/workflows/docker.yml
vendored
154
.github/workflows/docker.yml
vendored
|
|
@ -1,154 +0,0 @@
|
|||
name: Branch Build Using Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feature/*
|
||||
- hotfix/*
|
||||
- release/*
|
||||
- development
|
||||
|
||||
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: Pepperdash Core
|
||||
SOLUTION_FILE: PepperDash Core
|
||||
# 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:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
# First we checkout the source repo
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# And any submodules
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global url."https://github.com/".insteadOf "git@github.com:"
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
# Fetch all tags
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags
|
||||
# Generate the appropriate version number
|
||||
- name: Set Version Number
|
||||
shell: powershell
|
||||
run: |
|
||||
$version = ./.github/scripts/GenerateVersionNumber.ps1
|
||||
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
# Use the version number to set the version of the assemblies
|
||||
- name: Update AssemblyInfo.cs
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Output ${{ env.VERSION }}
|
||||
./.github/scripts/UpdateAssemblyVersion.ps1 ${{ env.VERSION }}
|
||||
# Login to Docker
|
||||
- name: Login to Docker
|
||||
uses: azure/docker-login@v1
|
||||
with:
|
||||
username: ${{ secrets.dockerhub_user }}
|
||||
password: ${{ secrets.dockerhub_password }}
|
||||
# Build the solutions in the docker image
|
||||
- name: Build Solution
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression "docker run --rm --mount type=bind,source=""$($Env:GITHUB_WORKSPACE)"",target=""c:/project"" pepperdash/sspbuilder c:\cihelpers\vsidebuild.exe -Solution ""c:\project\$($Env:SOLUTION_PATH)\$($Env:SOLUTION_FILE).sln"" -BuildSolutionConfiguration $($ENV:BUILD_TYPE)"
|
||||
- name: Zip Build Output
|
||||
shell: powershell
|
||||
run: ./.github/scripts/ZipBuildOutput.ps1
|
||||
# Write the version to a file to be consumed by the push jobs
|
||||
- name: Write Version
|
||||
run: Write-Output "$($Env:VERSION)" | Out-File -FilePath "$($Env:GITHUB_HOME)\output\version.txt"
|
||||
# Upload the build output as an artifact
|
||||
- name: Upload Build Output
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Build
|
||||
path: ./${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
# Upload the Version file as an artifact
|
||||
- name: Upload version.txt
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Version
|
||||
path: ${{env.GITHUB_HOME}}\output\version.txt
|
||||
# Create the release on the source repo
|
||||
- name: Create tag for non-rc builds
|
||||
if: contains(env.VERSION, 'alpha') || contains(env.VERSION, 'beta')
|
||||
run: |
|
||||
git tag $($Env:VERSION)
|
||||
git push --tags origin
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
# using contributor's version to allow for pointing at the right commit
|
||||
if: contains(env.VERSION,'-rc-') || contains(env.VERSION,'-hotfix-')
|
||||
uses: fleskesvor/create-release@feature/support-target-commitish
|
||||
with:
|
||||
tag_name: ${{ env.VERSION }}
|
||||
release_name: ${{ env.VERSION }}
|
||||
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Upload the build package to the release
|
||||
- name: Upload Release Package
|
||||
if: contains(env.VERSION,'-rc-') || contains(env.VERSION,'-hotfix-')
|
||||
id: upload_release
|
||||
uses: actions/upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
asset_name: ${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
asset_content_type: application/zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
|
||||
Push_Nuget_Package:
|
||||
needs: Build_Project
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Download Build Version Info
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: Version
|
||||
- name: Set Version Number
|
||||
shell: powershell
|
||||
run: |
|
||||
Get-ChildItem "./Version"
|
||||
$version = Get-Content -Path ./Version/version.txt
|
||||
Write-Host "Version: $version"
|
||||
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
Remove-Item -Path ./Version/version.txt
|
||||
Remove-Item -Path ./Version
|
||||
- name: Download Build output
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: Build
|
||||
path: ./
|
||||
- name: Unzip Build file
|
||||
run: |
|
||||
Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\
|
||||
Remove-Item -Path .\*.zip
|
||||
- name: Copy Files to root & delete output directory
|
||||
run: |
|
||||
Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz")
|
||||
Get-ChildItem -Path .\output\* | Copy-Item -Destination .\
|
||||
Remove-Item -Path .\output -Recurse
|
||||
- name: Add nuget.exe
|
||||
uses: nuget/setup-nuget@v1
|
||||
- name: Add Github Packages source
|
||||
run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Add nuget.org API Key
|
||||
run: nuget setApiKey ${{ secrets.NUGET_API_KEY }}
|
||||
- name: Create nuget package
|
||||
run: nuget pack "./PepperDash_Core.nuspec" -version ${{ env.VERSION }}
|
||||
- name: Publish nuget package to Github registry
|
||||
run: nuget push **/*.nupkg -source github
|
||||
- name: Publish nuget package to nuget.org
|
||||
run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||
129
.github/workflows/main.yml
vendored
129
.github/workflows/main.yml
vendored
|
|
@ -1,129 +0,0 @@
|
|||
name: Main Build using Docker
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
branches:
|
||||
- main
|
||||
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: Pepperdash Core
|
||||
SOLUTION_FILE: Pepperdash Core
|
||||
# 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@v2
|
||||
# And any submodules
|
||||
- name: Checkout submodules
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global url."https://github.com/".insteadOf "git@github.com:"
|
||||
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
|
||||
git submodule sync --recursive
|
||||
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
|
||||
# Generate the appropriate version number
|
||||
- name: Set Version Number
|
||||
shell: powershell
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: echo "VERSION=$($Env:TAG_NAME)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
# Use the version number to set the version of the assemblies
|
||||
- name: Update AssemblyInfo.cs
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Output ${{ env.VERSION }}
|
||||
./.github/scripts/UpdateAssemblyVersion.ps1 ${{ env.VERSION }}
|
||||
# Login to Docker
|
||||
- name: Login to Docker
|
||||
uses: azure/docker-login@v1
|
||||
with:
|
||||
username: ${{ secrets.dockerhub_user }}
|
||||
password: ${{ secrets.dockerhub_password }}
|
||||
# Build the solutions in the docker image
|
||||
- name: Build Solution
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-Expression "docker run --rm --mount type=bind,source=""$($Env:GITHUB_WORKSPACE)"",target=""c:/project"" pepperdash/sspbuilder c:\cihelpers\vsidebuild.exe -Solution ""c:\project\$($Env:SOLUTION_PATH)\$($Env:SOLUTION_FILE).sln"" -BuildSolutionConfiguration $($ENV:BUILD_TYPE)"
|
||||
# Zip up the output files as needed
|
||||
- name: Zip Build Output
|
||||
shell: powershell
|
||||
run: ./.github/scripts/ZipBuildOutput.ps1
|
||||
# Write the version to a file to be consumed by the push jobs
|
||||
- name: Write Version
|
||||
run: Write-Output "$($Env:VERSION)" | Out-File -FilePath "$($Env:GITHUB_HOME)\output\version.txt"
|
||||
- name: Upload Build Output
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Build
|
||||
path: ./${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
# Upload the Version file as an artifact
|
||||
- name: Upload version.txt
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: Version
|
||||
path: ${{env.GITHUB_HOME}}\output\version.txt
|
||||
# Upload the build package to the release
|
||||
- name: Upload Release Package
|
||||
id: upload_release
|
||||
uses: actions/upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
asset_name: ${{ env.SOLUTION_FILE}}-${{ env.VERSION}}.zip
|
||||
asset_content_type: application/zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
Push_Nuget_Package:
|
||||
needs: Build_Project
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Download Build Version Info
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: Version
|
||||
- name: Set Version Number
|
||||
shell: powershell
|
||||
run: |
|
||||
Get-ChildItem "./Version"
|
||||
$version = Get-Content -Path ./Version/version.txt
|
||||
Write-Host "Version: $version"
|
||||
echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
Remove-Item -Path ./Version/version.txt
|
||||
Remove-Item -Path ./Version
|
||||
- name: Download Build output
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: Build
|
||||
path: ./
|
||||
- name: Unzip Build file
|
||||
run: |
|
||||
Get-ChildItem .\*.zip | Expand-Archive -DestinationPath .\
|
||||
Remove-Item -Path .\*.zip
|
||||
- name: Copy Files to root & delete output directory
|
||||
run: |
|
||||
Remove-Item -Path .\* -Include @("*.cpz","*.md","*.cplz","*.json","*.dll","*.clz")
|
||||
Get-ChildItem -Path .\output\* | Copy-Item -Destination .\
|
||||
Remove-Item -Path .\output -Recurse
|
||||
- name: Add nuget.exe
|
||||
uses: nuget/setup-nuget@v1
|
||||
- name: Add Github Packages source
|
||||
run: nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username Pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Add nuget.org API Key
|
||||
run: nuget setApiKey ${{ secrets.NUGET_API_KEY }}
|
||||
- name: Create nuget package
|
||||
run: nuget pack "./PepperDash_Core.nuspec" -version ${{ env.VERSION }}
|
||||
- name: Publish nuget package to Github registry
|
||||
run: nuget push **/*.nupkg -source github
|
||||
- name: Publish nuget package to nuget.org
|
||||
run: nuget push **/*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||
412
.gitignore
vendored
412
.gitignore
vendored
|
|
@ -1,25 +1,401 @@
|
|||
#ignore thumbnails created by windows
|
||||
Thumbs.db
|
||||
#Ignore files build by Visual Studio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.aps
|
||||
*.pch
|
||||
*.vspscc
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ncb
|
||||
*.suo
|
||||
*.bak
|
||||
*.cache
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.log
|
||||
[Bb]in/
|
||||
[Dd]ebug*/
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
obj/
|
||||
[Rr]elease*/
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
SIMPLSharpLogs/
|
||||
*.projectinfo
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
lib/
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
*.projectinfo
|
||||
|
||||
output/
|
||||
36
.releaserc.json
Normal file
36
.releaserc.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
25
PepperDash.Core.4Series.sln
Normal file
25
PepperDash.Core.4Series.sln
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32228.430
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core.4Series", "src\PepperDash.Core.4Series.csproj", "{100ABA44-9471-4B18-8092-4D94D7D82223}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{100ABA44-9471-4B18-8092-4D94D7D82223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{100ABA44-9471-4B18-8092-4D94D7D82223}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{100ABA44-9471-4B18-8092-4D94D7D82223}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{100ABA44-9471-4B18-8092-4D94D7D82223}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E4615FA3-8C8C-4DC0-897B-E85408B4E341}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash_Core", "Pepperdash Core\PepperDash_Core.csproj", "{87E29B4C-569B-4368-A4ED-984AC1440C96}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash_Core", "Pepperdash Core\PepperDash_Core.csproj", "{87E29B4C-569B-4368-A4ED-984AC1440C96}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,684 +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 Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace DynamicTCP
|
||||
{
|
||||
public class DynamicTCPServer : Device
|
||||
{
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// Event for Receiving text
|
||||
/// </summary>
|
||||
public event EventHandler<CopyCoreForSimplpGenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event for client connection socket status change
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
/// Event for Server State Change
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
||||
#endregion
|
||||
|
||||
#region Properties/Variables
|
||||
/// <summary>
|
||||
/// Secure or unsecure TCP server. Defaults to Unsecure or standard TCP server without SSL
|
||||
/// </summary>
|
||||
public bool Secure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Helper for Secure bool. Parameter in SIMPL+ so there is no get, one way set from simpl+ Param to property in func main of SIMPL+
|
||||
/// </summary>
|
||||
public ushort USecure
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == 1)
|
||||
Secure = true;
|
||||
else if (value == 0)
|
||||
Secure = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text representation of the Socket Status enum values for the server
|
||||
/// </summary>
|
||||
public string Status
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||
return Secure ? SecureServer.State.ToString() : UnsecureServer.State.ToString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||
(Secure ? SecureServer.State == ServerState.SERVER_CONNECTED : UnsecureServer.State == ServerState.SERVER_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsListening
|
||||
{
|
||||
get { return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||
(Secure ? SecureServer.State == ServerState.SERVER_LISTENING : UnsecureServer.State == ServerState.SERVER_LISTENING); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsListening
|
||||
{
|
||||
get { return (ushort)(IsListening ? 1 : 0); }
|
||||
}
|
||||
|
||||
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
|
||||
/// <summary>
|
||||
/// Number of clients currently connected.
|
||||
/// </summary>
|
||||
public ushort NumberOfClientsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||
return Secure ? (ushort)SecureServer.NumberOfClientsConnected : (ushort)UnsecureServer.NumberOfClientsConnected;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port Server should listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for Port
|
||||
/// </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. Must be set the same in the client, and if true shared keys must be identical on server/client
|
||||
/// </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.
|
||||
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received
|
||||
/// </summary>
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Helper for Heartbeat Required
|
||||
/// </summary>
|
||||
public ushort UHeartbeatRequired
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == 1)
|
||||
HeartbeatRequired = true;
|
||||
else
|
||||
HeartbeatRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||
/// </summary>
|
||||
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Simpl+ Heartbeat Analog value in seconds
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||
|
||||
/// <summary>
|
||||
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
||||
/// </summary>
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
//private timers for Heartbeats per client
|
||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||
|
||||
//flags to show the secure server is waiting for client at index to send the shared key
|
||||
List<uint> WaitingForSharedKey = new List<uint>();
|
||||
|
||||
//Store the connected client indexes
|
||||
List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Private flag to note that the server has stopped intentionally
|
||||
/// </summary>
|
||||
private bool ServerStopped { get; set; }
|
||||
|
||||
//Servers
|
||||
SecureTCPServer SecureServer;
|
||||
TCPServer UnsecureServer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
public DynamicTCPServer()
|
||||
: base("Uninitialized Dynamic TCP Server")
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
BufferSize = 2000;
|
||||
Secure = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - Server Actions
|
||||
/// <summary>
|
||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start listening on the specified port
|
||||
/// </summary>
|
||||
public void Listen()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': Invalid port", Key);
|
||||
ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': Invalid port", Key));
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': No Shared Key set", Key);
|
||||
ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': No Shared Key set", Key));
|
||||
return;
|
||||
}
|
||||
if (IsListening)
|
||||
return;
|
||||
if (Secure)
|
||||
{
|
||||
SecureServer = new SecureTCPServer(Port, MaxClients);
|
||||
SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange);
|
||||
ServerStopped = false;
|
||||
SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||
onServerStateChange();
|
||||
Debug.Console(2, "Secure Server Status: {0}, Socket Status: {1}\r\n", SecureServer.State.ToString(), SecureServer.ServerSocketStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsecureServer = new TCPServer(Port, MaxClients);
|
||||
UnsecureServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(UnsecureServer_SocketStatusChange);
|
||||
ServerStopped = false;
|
||||
UnsecureServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||
onServerStateChange();
|
||||
Debug.Console(2, "Unsecure Server Status: {0}, Socket Status: {1}\r\n", UnsecureServer.State.ToString(), UnsecureServer.ServerSocketStatus);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop Listeneing
|
||||
/// </summary>
|
||||
public void StopListening()
|
||||
{
|
||||
Debug.Console(2, "Stopping Listener");
|
||||
if (SecureServer != null)
|
||||
SecureServer.Stop();
|
||||
if (UnsecureServer != null)
|
||||
UnsecureServer.Stop();
|
||||
ServerStopped = true;
|
||||
onServerStateChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect All Clients
|
||||
/// </summary>
|
||||
public void DisconnectAllClients()
|
||||
{
|
||||
Debug.Console(2, "Disconnecting All Clients");
|
||||
if (SecureServer != null)
|
||||
SecureServer.DisconnectAll();
|
||||
if (UnsecureServer != null)
|
||||
UnsecureServer.DisconnectAll();
|
||||
onConnectionChange();
|
||||
onServerStateChange(); //State shows both listening and connected
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast text from server to all connected clients
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void BroadcastText(string text)
|
||||
{
|
||||
if (ConnectedClientsIndexes.Count > 0)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
if (Secure)
|
||||
foreach (uint i in ConnectedClientsIndexes)
|
||||
SecureServer.SendDataAsync(i, b, b.Length, SecureSendDataAsyncCallback);
|
||||
else
|
||||
foreach (uint i in ConnectedClientsIndexes)
|
||||
UnsecureServer.SendDataAsync(i, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not sure this is useful in library, maybe Pro??
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
public void SendTextToClient(string text, uint clientIndex)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
if (Secure)
|
||||
SecureServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback);
|
||||
else
|
||||
UnsecureServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||
}
|
||||
|
||||
//private method to check heartbeat requirements and start or reset timer
|
||||
void checkHeartbeat(uint clientIndex, string received)
|
||||
{
|
||||
if (HeartbeatRequired)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(HeartbeatStringToMatch))
|
||||
{
|
||||
if (received == HeartbeatStringToMatch)
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - HeartbeatTimer Callback
|
||||
|
||||
void HeartbeatTimer_CallbackFunction(object o)
|
||||
{
|
||||
uint clientIndex = (uint)o;
|
||||
|
||||
string address = Secure ? SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex) :
|
||||
UnsecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||
|
||||
ErrorLog.Error("Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
||||
Debug.Console(2, "Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address);
|
||||
|
||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||
|
||||
if (Secure)
|
||||
SecureServer.Disconnect(clientIndex);
|
||||
else
|
||||
UnsecureServer.Disconnect(clientIndex);
|
||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods - Socket Status Changed Callbacks
|
||||
/// <summary>
|
||||
/// Secure Server Socket Status Changed Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="serverSocketStatus"></param>
|
||||
void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||
{
|
||||
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||
WaitingForSharedKey.Add(clientIndex);
|
||||
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||
ConnectedClientsIndexes.Add(clientIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||
ConnectedClientsIndexes.Remove(clientIndex);
|
||||
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||
}
|
||||
if(SecureServer.ServerSocketStatus.ToString() != Status)
|
||||
onConnectionChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP Server (Unsecure) Socket Status Change Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="serverSocketStatus"></param>
|
||||
void UnsecureServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus)
|
||||
{
|
||||
Debug.Console(2, "Client at {0} ServerSocketStatus {1}",
|
||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString());
|
||||
if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex))
|
||||
WaitingForSharedKey.Add(clientIndex);
|
||||
if (!ConnectedClientsIndexes.Contains(clientIndex))
|
||||
ConnectedClientsIndexes.Add(clientIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||
ConnectedClientsIndexes.Remove(clientIndex);
|
||||
if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
HeartbeatTimerDictionary.Remove(clientIndex);
|
||||
}
|
||||
if (UnsecureServer.ServerSocketStatus.ToString() != Status)
|
||||
onConnectionChange();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods Connected Callbacks
|
||||
/// <summary>
|
||||
/// Secure TCP Client Connected to Secure Server Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
void SecureConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex)
|
||||
{
|
||||
if (mySecureTCPServer.ClientConnected(clientIndex))
|
||||
{
|
||||
if (SharedKeyRequired)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback);
|
||||
Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
}
|
||||
if (HeartbeatRequired)
|
||||
{
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||
if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||
mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure TCP Client Connected to Unsecure Server Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
void UnsecureConnectCallback(TCPServer myTCPServer, uint clientIndex)
|
||||
{
|
||||
if (myTCPServer.ClientConnected(clientIndex))
|
||||
{
|
||||
if (SharedKeyRequired)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n");
|
||||
myTCPServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback);
|
||||
Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
}
|
||||
if (HeartbeatRequired)
|
||||
{
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback);
|
||||
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||
myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||
}
|
||||
if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||
myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - Send/Receive Callbacks
|
||||
/// <summary>
|
||||
/// Secure Send Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesSent"></param>
|
||||
void SecureSendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||
{
|
||||
//Seems there is nothing to do here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure Send Data Asyc Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesSent"></param>
|
||||
void UnsecureSendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||
{
|
||||
//Seems there is nothing to do here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Secure Received Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesReceived"></param>
|
||||
void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||
{
|
||||
if (numberOfBytesReceived > 0)
|
||||
{
|
||||
string received = "Nothing";
|
||||
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||
if (WaitingForSharedKey.Contains(clientIndex))
|
||||
{
|
||||
received = received.Replace("\r", "");
|
||||
received = received.Replace("\n", "");
|
||||
if (received != SharedKey)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||
Debug.Console(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||
mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||
mySecureTCPServer.Disconnect(clientIndex);
|
||||
}
|
||||
if (mySecureTCPServer.NumberOfClientsConnected > 0)
|
||||
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||
WaitingForSharedKey.Remove(clientIndex);
|
||||
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||
mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback);
|
||||
Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
||||
mySecureTCPServer.PortNumber, mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received);
|
||||
onTextReceived(received);
|
||||
}
|
||||
checkHeartbeat(clientIndex, received);
|
||||
}
|
||||
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure Received Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesReceived"></param>
|
||||
void UnsecureReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
|
||||
{
|
||||
if (numberOfBytesReceived > 0)
|
||||
{
|
||||
string received = "Nothing";
|
||||
byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
|
||||
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
|
||||
if (WaitingForSharedKey.Contains(clientIndex))
|
||||
{
|
||||
received = received.Replace("\r", "");
|
||||
received = received.Replace("\n", "");
|
||||
if (received != SharedKey)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||
Debug.Console(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||
ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex);
|
||||
myTCPServer.SendDataAsync(clientIndex, b, b.Length, null);
|
||||
myTCPServer.Disconnect(clientIndex);
|
||||
}
|
||||
if (myTCPServer.NumberOfClientsConnected > 0)
|
||||
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||
WaitingForSharedKey.Remove(clientIndex);
|
||||
byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication");
|
||||
myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null);
|
||||
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback);
|
||||
Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n",
|
||||
myTCPServer.PortNumber, myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received);
|
||||
onTextReceived(received);
|
||||
}
|
||||
checkHeartbeat(clientIndex, received);
|
||||
}
|
||||
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - EventHelpers/Callbacks
|
||||
//Private Helper method to call the Connection Change Event
|
||||
void onConnectionChange()
|
||||
{
|
||||
var handler = ClientConnectionChange;
|
||||
if (handler != null)
|
||||
{
|
||||
if (Secure)
|
||||
handler(this, new DynamicTCPSocketStatusChangeEventArgs(SecureServer, Secure));
|
||||
else
|
||||
handler(this, new DynamicTCPSocketStatusChangeEventArgs(UnsecureServer, Secure));
|
||||
}
|
||||
}
|
||||
|
||||
//Private Helper Method to call the Text Received Event
|
||||
void onTextReceived(string text)
|
||||
{
|
||||
var handler = TextReceived;
|
||||
if (handler != null)
|
||||
handler(this, new CopyCoreForSimplpGenericCommMethodReceiveTextArgs(text));
|
||||
}
|
||||
|
||||
//Private Helper Method to call the Server State Change Event
|
||||
void onServerStateChange()
|
||||
{
|
||||
var handler = ServerStateChange;
|
||||
if(handler != null)
|
||||
{
|
||||
if(Secure)
|
||||
handler(this, new DynamicTCPServerStateChangedEventArgs(SecureServer, Secure));
|
||||
else
|
||||
handler(this, new DynamicTCPServerStateChangedEventArgs(UnsecureServer, Secure));
|
||||
}
|
||||
}
|
||||
|
||||
//Private Event Handler method to handle the closing of connections when the program stops
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
Debug.Console(1, this, "Program stopping. Closing server");
|
||||
DisconnectAllClients();
|
||||
StopListening();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Background class that manages debug features for sockets
|
||||
/// </summary>
|
||||
public static class CommStatic
|
||||
{
|
||||
static List<ISocketStatus> Sockets = new List<ISocketStatus>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the backing class. Adds console commands for S#Pro programs
|
||||
/// </summary>
|
||||
static CommStatic()
|
||||
{
|
||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||
{
|
||||
CrestronConsole.AddNewConsoleCommand(SocketCommand, "socket", "socket commands: list, send, connect, disco",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
}
|
||||
}
|
||||
|
||||
static void SocketCommand(string s)
|
||||
{
|
||||
// 0 1 2
|
||||
//socket command number/key/all param
|
||||
//socket list
|
||||
//socket send 4 ver -v\n
|
||||
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return;
|
||||
var tokens = s.Split(' ');
|
||||
if (tokens.Length == 0)
|
||||
return;
|
||||
var command = tokens[0].ToLower();
|
||||
if(command == "connect")
|
||||
{
|
||||
|
||||
}
|
||||
else if(command == "disco")
|
||||
{
|
||||
|
||||
}
|
||||
else if(command =="list")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("{0} sockets", Sockets.Count);
|
||||
if(Sockets.Count == 0)
|
||||
return;
|
||||
// get the longest key name, for formatting
|
||||
var longestLength = Sockets.Aggregate("",
|
||||
(max, cur) => max.Length > cur.Key.Length ? max : cur.Key).Length;
|
||||
for(int i = 0; i < Sockets.Count; i++)
|
||||
{
|
||||
var sock = Sockets[i];
|
||||
CrestronConsole.ConsoleCommandResponse("{0} {1} {2} {3}",
|
||||
i, sock.Key, GetSocketType(sock), sock.ClientStatus);
|
||||
}
|
||||
}
|
||||
else if(command == "send")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for socket list, to show types
|
||||
/// </summary>
|
||||
static string GetSocketType(ISocketStatus sock)
|
||||
{
|
||||
if (sock is GenericSshClient)
|
||||
return "SSH";
|
||||
else if (sock is GenericTcpIpClient)
|
||||
return "TCP-IP";
|
||||
else
|
||||
return "?";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
public static void AddSocket(ISocketStatus socket)
|
||||
{
|
||||
if(!Sockets.Contains(socket))
|
||||
Sockets.Add(socket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
public static void RemoveSocket(ISocketStatus socket)
|
||||
{
|
||||
if (Sockets.Contains(socket))
|
||||
Sockets.Remove(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Client for communicating with an HTTP Server Side Event pattern
|
||||
/// </summary>
|
||||
public class GenericHttpSseClient : ICommunicationReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies when bytes have been received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Notifies when text has been received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates connection status
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the instance
|
||||
/// </summary>
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name for the instance
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL of the server
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
HttpClient Client;
|
||||
HttpClientRequest Request;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
public GenericHttpSseClient(string key, string name)
|
||||
{
|
||||
Key = key;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the server. Requires Url to be set first.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
InitiateConnection(Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
CloseConnection(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates connection to the server
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
public void InitiateConnection(string url)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if(string.IsNullOrEmpty(url))
|
||||
{
|
||||
Debug.Console(0, this, "Error connecting to Server. No URL specified");
|
||||
return;
|
||||
}
|
||||
|
||||
Client = new HttpClient();
|
||||
Request = new HttpClientRequest();
|
||||
Client.Verbose = true;
|
||||
Client.KeepAlive = true;
|
||||
Request.Url.Parse(url);
|
||||
Request.RequestType = RequestType.Get;
|
||||
Request.Header.SetHeaderValue("Accept", "text/event-stream");
|
||||
|
||||
// In order to get a handle on the response stream, we have to get
|
||||
// the request stream first. Boo
|
||||
Client.BeginGetRequestStream(GetRequestStreamCallback, Request, null);
|
||||
CrestronConsole.PrintLine("Request made!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorLog.Notice("Exception occured in AsyncWebPostHttps(): " + e.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the server
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
public void CloseConnection(string s)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
Client.Abort();
|
||||
IsConnected = false;
|
||||
|
||||
Debug.Console(1, this, "Client Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRequestStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
// End the the async request operation and return the data stream
|
||||
Stream requestStream = request.ThisClient.EndGetRequestStream(request, null);
|
||||
// If this were something other than a GET we could write to the stream here
|
||||
|
||||
// Closing makes the request happen
|
||||
requestStream.Close();
|
||||
|
||||
// Get a handle on the response stream.
|
||||
request.ThisClient.BeginGetResponseStream(GetResponseStreamCallback, request, status);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <param name="status"></param>
|
||||
private void GetResponseStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This closes up the GetResponseStream async
|
||||
var response = request.ThisClient.EndGetResponseStream(request);
|
||||
|
||||
response.DataConnection.OnBytesReceived += new EventHandler(DataConnection_OnBytesReceived);
|
||||
|
||||
IsConnected = true;
|
||||
|
||||
Debug.Console(1, this, "Client Disconnected");
|
||||
|
||||
Stream streamResponse = response.ContentStream;
|
||||
// Object containing various states to be passed back to async callback below
|
||||
RequestState asyncState = new RequestState();
|
||||
asyncState.Request = request;
|
||||
asyncState.Response = response;
|
||||
asyncState.StreamResponse = streamResponse;
|
||||
asyncState.HttpClient = request.ThisClient;
|
||||
|
||||
// This processes the ongoing data stream
|
||||
Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult = null;
|
||||
do
|
||||
{
|
||||
asyncResult = streamResponse.BeginRead(asyncState.BufferRead, 0, RequestState.BUFFER_SIZE,
|
||||
new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), asyncState);
|
||||
}
|
||||
while (asyncResult.CompletedSynchronously && !asyncState.Done);
|
||||
|
||||
//Console.WriteLine("\r\nExit Response Callback\r\n");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void DataConnection_OnBytesReceived(object sender, EventArgs e)
|
||||
{
|
||||
Debug.Console(1, this, "DataConnection OnBytesReceived Fired");
|
||||
}
|
||||
|
||||
private void ReadCallBack(Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult)
|
||||
{
|
||||
//we are getting back everything here, so cast the state from the call
|
||||
RequestState requestState = asyncResult.AsyncState as RequestState;
|
||||
Stream responseStream = requestState.StreamResponse;
|
||||
|
||||
int read = responseStream.EndRead(asyncResult);
|
||||
// Read the HTML page and then print it to the console.
|
||||
if (read > 0)
|
||||
{
|
||||
var bytes = requestState.BufferRead;
|
||||
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
|
||||
//requestState.RequestData.Append(Encoding.ASCII.GetString(requestState.BufferRead, 0, read));
|
||||
//CrestronConsole.PrintLine(requestState.RequestData.ToString());
|
||||
|
||||
//clear the byte array buffer used.
|
||||
Array.Clear(requestState.BufferRead, 0, requestState.BufferRead.Length);
|
||||
|
||||
if (asyncResult.CompletedSynchronously)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Crestron.SimplSharp.CrestronIO.IAsyncResult asynchronousResult;
|
||||
do
|
||||
{
|
||||
asynchronousResult = responseStream.BeginRead(requestState.BufferRead, 0, RequestState.BUFFER_SIZE,
|
||||
new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), requestState);
|
||||
}
|
||||
while (asynchronousResult.CompletedSynchronously && !requestState.Done);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestState.Done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the state of the request
|
||||
/// </summary>
|
||||
public class RequestState
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const int BUFFER_SIZE = 10000;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] BufferRead;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClient HttpClient;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClientRequest Request;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClientResponse Response;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Stream StreamResponse;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Done;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public RequestState()
|
||||
{
|
||||
BufferRead = new byte[BUFFER_SIZE];
|
||||
HttpClient = null;
|
||||
Request = null;
|
||||
Response = null;
|
||||
StreamResponse = null;
|
||||
Done = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waithandle for main thread.
|
||||
/// </summary>
|
||||
public class StreamAsyncTest
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public CEvent wait_for_response = new CEvent(true, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,351 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for two simultaneous TCP clients to connect to a redundant pair of QSC Core DSPs and manages
|
||||
/// </summary>
|
||||
public class QscCoreDoubleTcpIpClient : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Key to uniquely identify the instance of the class
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when a bool value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||
/// <summary>
|
||||
/// Fires when a ushort value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<UshrtChangeEventArgs> UshortChange;
|
||||
/// <summary>
|
||||
/// Fires when a string value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||
|
||||
/// <summary>
|
||||
/// The client for the master DSP unit
|
||||
/// </summary>
|
||||
public GenericTcpIpClient MasterClient { get; private set; }
|
||||
/// <summary>
|
||||
/// The client for the slave DSP unit
|
||||
/// </summary>
|
||||
public GenericTcpIpClient SlaveClient { get; private set; }
|
||||
|
||||
string Username;
|
||||
string Password;
|
||||
string LineEnding;
|
||||
|
||||
CommunicationGather MasterGather;
|
||||
CommunicationGather SlaveGather;
|
||||
|
||||
bool IsPolling;
|
||||
int PollingIntervalSeconds;
|
||||
CTimer PollTimer;
|
||||
|
||||
bool SlaveIsActive;
|
||||
|
||||
/// <summary>
|
||||
/// Default constuctor for S+
|
||||
/// </summary>
|
||||
public QscCoreDoubleTcpIpClient()
|
||||
{
|
||||
MasterClient = new GenericTcpIpClient("temp-master");
|
||||
MasterClient.AutoReconnect = true;
|
||||
MasterClient.AutoReconnectIntervalMs = 2000;
|
||||
SlaveClient = new GenericTcpIpClient("temp-slave");
|
||||
SlaveClient.AutoReconnect = true;
|
||||
SlaveClient.AutoReconnectIntervalMs = 2000;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to both DSP units
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="masterAddress"></param>
|
||||
/// <param name="masterPort"></param>
|
||||
/// <param name="slaveAddress"></param>
|
||||
/// <param name="slavePort"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="pollingIntervalSeconds"></param>
|
||||
/// <param name="lineEnding"></param>
|
||||
public void Connect(string key, string masterAddress, int masterPort,
|
||||
string slaveAddress, int slavePort, string username, string password,
|
||||
int pollingIntervalSeconds, string lineEnding)
|
||||
{
|
||||
Key = key;
|
||||
|
||||
PollingIntervalSeconds = pollingIntervalSeconds;
|
||||
Username = username;
|
||||
Password = password;
|
||||
LineEnding = lineEnding;
|
||||
|
||||
MasterClient.Initialize(key + "-master");
|
||||
SlaveClient.Initialize(key + "-slave");
|
||||
|
||||
MasterClient.Hostname = masterAddress;
|
||||
MasterClient.Port = masterPort;
|
||||
|
||||
if (MasterClient != null)
|
||||
{
|
||||
MasterClient.Disconnect();
|
||||
}
|
||||
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
SlaveClient.Disconnect();
|
||||
}
|
||||
|
||||
if (MasterGather == null)
|
||||
{
|
||||
MasterGather = new CommunicationGather(MasterClient, lineEnding);
|
||||
MasterGather.IncludeDelimiter = true;
|
||||
}
|
||||
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
MasterGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(MasterGather_LineReceived);
|
||||
|
||||
MasterClient.ConnectionChange -= MasterClient_SocketStatusChange;
|
||||
MasterClient.ConnectionChange += MasterClient_SocketStatusChange;
|
||||
|
||||
SlaveClient.Hostname = slaveAddress;
|
||||
SlaveClient.Port = slavePort;
|
||||
|
||||
if (SlaveGather == null)
|
||||
{
|
||||
SlaveGather = new CommunicationGather(SlaveClient, lineEnding);
|
||||
SlaveGather.IncludeDelimiter = true;
|
||||
}
|
||||
|
||||
SlaveGather.LineReceived -= MasterGather_LineReceived;
|
||||
SlaveGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(SlaveGather_LineReceived);
|
||||
|
||||
SlaveClient.ConnectionChange -= SlaveClient_SocketStatusChange;
|
||||
SlaveClient.ConnectionChange += SlaveClient_SocketStatusChange;
|
||||
|
||||
MasterClient.Connect();
|
||||
SlaveClient.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (MasterClient != null)
|
||||
{
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
MasterClient.Disconnect();
|
||||
}
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
SlaveGather.LineReceived -= SlaveGather_LineReceived;
|
||||
SlaveClient.Disconnect();
|
||||
}
|
||||
if (PollTimer != null)
|
||||
{
|
||||
IsPolling = false;
|
||||
|
||||
PollTimer.Stop();
|
||||
PollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not include line feed
|
||||
/// </summary>
|
||||
public void SendText(string s)
|
||||
{
|
||||
if (SlaveIsActive)
|
||||
{
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
Debug.Console(2, this, "Sending to Slave: {0}", s);
|
||||
SlaveClient.SendText(s);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MasterClient != null)
|
||||
{
|
||||
Debug.Console(2, this, "Sending to Master: {0}", s);
|
||||
MasterClient.SendText(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MasterClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args)
|
||||
{
|
||||
OnUshortChange((ushort)args.Client.ClientStatus, MasterClientStatusId);
|
||||
|
||||
if (args.Client.IsConnected)
|
||||
{
|
||||
MasterGather.LineReceived += MasterGather_LineReceived;
|
||||
|
||||
StartPolling();
|
||||
}
|
||||
else
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
}
|
||||
|
||||
void SlaveClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args)
|
||||
{
|
||||
OnUshortChange((ushort)args.Client.ClientStatus, SlaveClientStatusId);
|
||||
|
||||
if (args.Client.IsConnected)
|
||||
{
|
||||
SlaveGather.LineReceived += SlaveGather_LineReceived;
|
||||
|
||||
StartPolling();
|
||||
}
|
||||
else
|
||||
SlaveGather.LineReceived -= SlaveGather_LineReceived;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MasterGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
|
||||
{
|
||||
if (e.Text.Contains("login_required"))
|
||||
{
|
||||
MasterClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password));
|
||||
}
|
||||
else if (e.Text.Contains("login_success"))
|
||||
{
|
||||
// START THE POLLING, YO!
|
||||
}
|
||||
else if (e.Text.StartsWith("sr"))
|
||||
{
|
||||
// example response "sr "MyDesign" "NIEC2bxnVZ6a" 1 1"
|
||||
|
||||
var split = e.Text.Trim().Split(' ');
|
||||
if (split[split.Length - 1] == "1")
|
||||
{
|
||||
SlaveIsActive = false;
|
||||
OnBoolChange(false, SlaveIsActiveId);
|
||||
OnBoolChange(true, MasterIsActiveId);
|
||||
}
|
||||
}
|
||||
if (!SlaveIsActive)
|
||||
OnStringChange(e.Text, LineReceivedId);
|
||||
}
|
||||
|
||||
void SlaveGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
|
||||
{
|
||||
if (e.Text.Contains("login_required"))
|
||||
{
|
||||
SlaveClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password));
|
||||
}
|
||||
else if (e.Text.Contains("login_success"))
|
||||
{
|
||||
// START THE POLLING, YO!
|
||||
}
|
||||
else if (e.Text.StartsWith("sr"))
|
||||
{
|
||||
var split = e.Text.Trim().Split(' ');
|
||||
if (split[split.Length - 1] == "1")
|
||||
{
|
||||
SlaveIsActive = true;
|
||||
OnBoolChange(true, SlaveIsActiveId);
|
||||
OnBoolChange(false, MasterIsActiveId);
|
||||
}
|
||||
}
|
||||
if (SlaveIsActive)
|
||||
OnStringChange(e.Text, LineReceivedId);
|
||||
}
|
||||
|
||||
void StartPolling()
|
||||
{
|
||||
if (!IsPolling)
|
||||
{
|
||||
IsPolling = true;
|
||||
|
||||
Poll();
|
||||
if (PollTimer != null)
|
||||
PollTimer.Stop();
|
||||
|
||||
PollTimer = new CTimer(o => Poll(), null, PollingIntervalSeconds * 1000, PollingIntervalSeconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void Poll()
|
||||
{
|
||||
if (MasterClient != null && MasterClient.IsConnected)
|
||||
{
|
||||
Debug.Console(2, this, "Polling Master.");
|
||||
MasterClient.SendText("sg\x0d\x0a");
|
||||
|
||||
}
|
||||
if (SlaveClient != null && SlaveClient.IsConnected)
|
||||
{
|
||||
Debug.Console(2, this, "Polling Slave.");
|
||||
SlaveClient.SendText("sg\x0d\x0a");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// login NAME PIN ---> login_success, login_failed
|
||||
|
||||
// status get
|
||||
// sg --> sr DESIGN_NAME DESIGN_ID IS_PRIMARY IS_ACTIVE
|
||||
|
||||
// CRLF
|
||||
|
||||
void OnBoolChange(bool state, ushort type)
|
||||
{
|
||||
var handler = BoolChange;
|
||||
if (handler != null)
|
||||
handler(this, new BoolChangeEventArgs(state, type));
|
||||
}
|
||||
|
||||
void OnUshortChange(ushort state, ushort type)
|
||||
{
|
||||
var handler = UshortChange;
|
||||
if (handler != null)
|
||||
handler(this, new UshrtChangeEventArgs(state, type));
|
||||
}
|
||||
|
||||
void OnStringChange(string value, ushort type)
|
||||
{
|
||||
var handler = StringChange;
|
||||
if (handler != null)
|
||||
handler(this, new StringChangeEventArgs(value, type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MasterIsActiveId = 3;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort SlaveIsActiveId = 4;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MainModuleInitiailzeId = 5;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MasterClientStatusId = 101;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort SlaveClientStatusId = 102;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort LineReceivedId = 201;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
//using System;
|
||||
////using System.IO;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using Crestron.SimplSharp;
|
||||
//using Crestron.SimplSharp.CrestronIO;
|
||||
//using Newtonsoft.Json;
|
||||
//using Newtonsoft.Json.Linq;
|
||||
|
||||
//namespace PepperDash.Core.JsonToSimpl
|
||||
//{
|
||||
|
||||
// public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
||||
// {
|
||||
// public JsonToSimplFixedPathObject()
|
||||
// {
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement
|
||||
{
|
||||
// Example JSON password array configuration object
|
||||
//{
|
||||
// "global":{
|
||||
// "passwords":[
|
||||
// {
|
||||
// "key": "Password01",
|
||||
// "name": "Technician Password",
|
||||
// "enabled": true,
|
||||
// "password": "1988"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// JSON password array configuration object
|
||||
/// </summary>
|
||||
//public class PasswordConfig
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// Key used to search for object in JSON array
|
||||
// /// </summary>
|
||||
// public string key { get; set; }
|
||||
// /// <summary>
|
||||
// /// Friendly name of password object
|
||||
// /// </summary>
|
||||
// public string name { get; set; }
|
||||
// /// <summary>
|
||||
// /// Password object enabled
|
||||
// /// </summary>
|
||||
// public bool enabled { get; set; }
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// public ushort simplEnabled
|
||||
// {
|
||||
// get { return (ushort)(enabled ? 1 : 0); }
|
||||
// set { enabled = Convert.ToBoolean(value); }
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// Password object configured password
|
||||
// /// </summary>
|
||||
// public string password { get; set; }
|
||||
// /// <summary>
|
||||
// /// Password type
|
||||
// /// </summary>
|
||||
// private int type { get; set; }
|
||||
// /// <summary>
|
||||
// /// Password Type for S+
|
||||
// /// </summary>
|
||||
// public ushort simplType
|
||||
// {
|
||||
// get { return Convert.ToUInt16(type); }
|
||||
// set { type = value; }
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// Password path
|
||||
// /// **FUTURE** implementation of saving passwords recieved from Fusion or other external sources back to config
|
||||
// /// </summary>
|
||||
// public string path { get; set; }
|
||||
// /// <summary>
|
||||
// /// Constructor
|
||||
// /// </summary>
|
||||
// public PasswordConfig()
|
||||
// {
|
||||
// simplEnabled = 0;
|
||||
// simplType = 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
// Example JSON password collections configuration object
|
||||
//{
|
||||
// "global": {
|
||||
// "passwords": {
|
||||
// "1": {
|
||||
// "name": "Technician Password",
|
||||
// "password": "2468"
|
||||
// },
|
||||
// "2": {
|
||||
// "name": "System Password",
|
||||
// "password": "123456"
|
||||
// },
|
||||
// "3": {
|
||||
// "name": "Master Password",
|
||||
// "password": "abc123"
|
||||
// },
|
||||
// "5": {
|
||||
// "name": "Backdoor Password",
|
||||
// "password": "1988"
|
||||
// },
|
||||
// "10": {
|
||||
// "name": "Backdoor Password",
|
||||
// "password": "1988"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// JSON password array configuration object
|
||||
/// </summary>
|
||||
public class PasswordConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Password object configured password
|
||||
/// </summary>
|
||||
public string password { get; set; }
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PasswordConfig()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Global JSON object
|
||||
/// </summary>
|
||||
//public class GlobalConfig
|
||||
//{
|
||||
// //public List<PasswordConfig> passwords { get; set; }
|
||||
// public Dictionary<uint, PasswordConfig> passwords { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Constructor
|
||||
// /// </summary>
|
||||
// public GlobalConfig()
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Root JSON object
|
||||
/// </summary>
|
||||
//public class RootObject
|
||||
//{
|
||||
// public GlobalConfig global { get; set; }
|
||||
//}
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement
|
||||
{
|
||||
public class PasswordClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Password Client
|
||||
/// </summary>
|
||||
public PasswordConfig Client { 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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize method
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange);
|
||||
|
||||
Client = new PasswordConfig();
|
||||
PasswordToValidate = "";
|
||||
|
||||
// there has to be a better way to get the index of the current index of password
|
||||
ushort i = 0;
|
||||
foreach (var password in PasswordManager.Passwords)
|
||||
{
|
||||
i++;
|
||||
OnUshrtChange((ushort)password.Key, (ushort)password.Key, PasswordManagementConstants.PasswordKey);
|
||||
}
|
||||
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves password by key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
//public void GetPasswordByKey(string key)
|
||||
//{
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// {
|
||||
// Debug.Console(1, "PassowrdClient.GetPasswordByKey failed:\rKey {0} is null or empty", key);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// PasswordConfig password = PasswordManager.Passwords.FirstOrDefault(p => p.key.Equals(key));
|
||||
// if (password == null)
|
||||
// {
|
||||
// OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Client = password;
|
||||
// OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength);
|
||||
// OnStringChange(Client.key, 0, PasswordManagementConstants.PasswordKeySelected);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve password by index
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void GetPasswordByIndex(ushort key)
|
||||
{
|
||||
PasswordConfig pw = PasswordManager.Passwords[key];
|
||||
if (pw == null)
|
||||
{
|
||||
OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength);
|
||||
return;
|
||||
}
|
||||
|
||||
Client = pw;
|
||||
OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength);
|
||||
OnUshrtChange(key, 0, PasswordManagementConstants.PasswordKeySelected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Password validation method
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
public void ValidatePassword(string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
return;
|
||||
|
||||
if (string.Equals(Client.password, password))
|
||||
{
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsValid);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsInvalid);
|
||||
}
|
||||
|
||||
|
||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsValid);
|
||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsInvalid);
|
||||
|
||||
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.PasswordLedChange);
|
||||
|
||||
if (PasswordToValidate.Length == Client.password.Length)
|
||||
ValidatePassword(PasswordToValidate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the user entered password and resets the LEDs
|
||||
/// </summary>
|
||||
public void ClearPassword()
|
||||
{
|
||||
PasswordToValidate = "";
|
||||
OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedChange);
|
||||
|
||||
for(var i = 1; i <= Client.password.Length; i++)
|
||||
OnBoolChange(false, (ushort)i, PasswordManagementConstants.PasswordLedChange);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core.JsonToSimpl;
|
||||
using PepperDash.Core.JsonStandardObjects;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement
|
||||
{
|
||||
public class PasswordManager
|
||||
{
|
||||
/// <summary>
|
||||
/// List of passwords configured
|
||||
/// </summary>
|
||||
public static Dictionary<uint, PasswordConfig> Passwords = new Dictionary<uint, PasswordConfig>();
|
||||
private Dictionary<uint, PasswordConfig> TempPasswords = new Dictionary<uint, PasswordConfig>();
|
||||
|
||||
CTimer UpdateTimer;
|
||||
public long UpdateTimerElapsedMs = 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>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PasswordManager()
|
||||
{
|
||||
Passwords.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize method
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="uniqueId"></param>
|
||||
//public void Initialize(string uniqueId, string key)
|
||||
//{
|
||||
// OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange);
|
||||
|
||||
// try
|
||||
// {
|
||||
// if(string.IsNullOrEmpty(uniqueId) || string.IsNullOrEmpty(key))
|
||||
// {
|
||||
// Debug.Console(1, "PasswordManager.Initialize({0}, {1}) null or empty parameters", uniqueId, key);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// JsonToSimplMaster master = J2SGlobal.GetMasterByFile(uniqueId);
|
||||
// if(master == null)
|
||||
// {
|
||||
// Debug.Console(1, "PassowrdManager.Initialize failed:\rCould not find JSON file with uniqueID {0}", uniqueId);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var global = master.JsonObject.ToObject<RootObject>().global;
|
||||
// var passwords = global.passwords;
|
||||
// if(passwords == null)
|
||||
// {
|
||||
// Debug.Console(1, "PasswordManager.Initialize failed:\rCould not find password object");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// foreach(var password in passwords)
|
||||
// {
|
||||
// if (password != null)
|
||||
// {
|
||||
// var index = passwords.IndexOf(password);
|
||||
|
||||
// password.path = string.Format("global.passwords[{0}]", index);
|
||||
// Debug.Console(1, "PasswordManager.Initialize: {0}, {1}, {2}, {3}, {4}, {5}", password.key, password.name, password.simplEnabled, password.simplType, password.password, password.path);
|
||||
// //AddPassword(password);
|
||||
|
||||
// OnStringChange(password.path, (ushort)index, PasswordManagementConstants.FullPathToPassword);
|
||||
// OnStringChange(password.key, (ushort)index, PasswordManagementConstants.PasswordKey);
|
||||
// }
|
||||
// }
|
||||
|
||||
// OnUshrtChange(Convert.ToUInt16(Passwords.Count), 0, PasswordManagementConstants.PasswordListCount);
|
||||
// }
|
||||
// catch(Exception e)
|
||||
// {
|
||||
// var msg = string.Format("PasswordManager.Initialize({0}, {1}) failed:\r{2}", uniqueId, key, e.Message);
|
||||
// CrestronConsole.PrintLine(msg);
|
||||
// ErrorLog.Error(msg);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange);
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Adds password to the list
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
//private void AddPassword(PasswordConfig password)
|
||||
//{
|
||||
// if (password == null)
|
||||
// return;
|
||||
|
||||
// var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key));
|
||||
// if (item != null)
|
||||
// Passwords.Remove(item);
|
||||
// Passwords.Add(password);
|
||||
|
||||
// Passwords.Sort((x, y) => string.Compare(x.key, y.key));
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Removes password from the list
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
//private void RemovePassword(PasswordConfig password)
|
||||
//{
|
||||
// if (password == null)
|
||||
// return;
|
||||
|
||||
// var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key));
|
||||
// if (item != null)
|
||||
// Passwords.Remove(item);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Updates password stored in the dictonary
|
||||
/// </summary>
|
||||
/// <param name="uniqueId"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="password"></param>
|
||||
public void UpdatePassword(ushort key, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
return;
|
||||
|
||||
var pw = TempPasswords[key];
|
||||
if (pw == null)
|
||||
{
|
||||
pw = new PasswordConfig();
|
||||
}
|
||||
pw.password = password;
|
||||
|
||||
if (UpdateTimer == null)
|
||||
{
|
||||
// (o) => SavePasswords removes the need to create a callback method that takes in an object
|
||||
UpdateTimer = new CTimer((o) => StorePassword(), UpdateTimerElapsedMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTimer.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the updated passwords in TempPassword in the Passwords dictionary
|
||||
/// </summary>
|
||||
private void StorePassword()
|
||||
{
|
||||
UpdateTimer.Stop();
|
||||
|
||||
foreach (var tempPw in TempPasswords)
|
||||
{
|
||||
Passwords[tempPw.Key] = tempPw.Value;
|
||||
}
|
||||
|
||||
TempPasswords.Clear();
|
||||
}
|
||||
|
||||
/// <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="state"></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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{87E29B4C-569B-4368-A4ED-984AC1440C96}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PepperDash.Core</RootNamespace>
|
||||
<AssemblyName>PepperDash_Core</AssemblyName>
|
||||
<ProjectTypeGuids>{0B4745B0-194B-4BB6-8E21-E9057CA92500};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<PlatformFamilyName>WindowsCE</PlatformFamilyName>
|
||||
<PlatformID>E2BECB1F-8C8C-41ba-B736-9BE7D946A398</PlatformID>
|
||||
<OSVersion>5.0</OSVersion>
|
||||
<DeployDirSuffix>SmartDeviceProject1</DeployDirSuffix>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<NativePlatformName>Windows CE</NativePlatformName>
|
||||
<FormFactorID>
|
||||
</FormFactorID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AllowedReferenceRelatedFileExtensions>.allowedReferenceRelatedFileExtensions</AllowedReferenceRelatedFileExtensions>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NoStdLib>true</NoStdLib>
|
||||
<NoConfig>true</NoConfig>
|
||||
<DocumentationFile>bin\PepperDash_Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<AllowedReferenceRelatedFileExtensions>.allowedReferenceRelatedFileExtensions</AllowedReferenceRelatedFileExtensions>
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NoStdLib>true</NoStdLib>
|
||||
<NoConfig>true</NoConfig>
|
||||
<DocumentationFile>bin\PepperDash_Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="SimplSharpCustomAttributesInterface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SimplSharpCWSHelperInterface, Version=2.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCWSHelperInterface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SimplSharpHelperInterface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SimplSharpNewtonsoft, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SimplSharpReflectionInterface, Version=1.0.5583.25238, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CommunicationExtras.cs" />
|
||||
<Compile Include="Comm\CommunicationStreamDebugging.cs" />
|
||||
<Compile Include="Comm\ControlPropertiesConfig.cs" />
|
||||
<Compile Include="Comm\GenericSecureTcpIpClient.cs" />
|
||||
<Compile Include="Comm\GenericTcpIpClient_ForServer.cs" />
|
||||
<Compile Include="Comm\GenericHttpSseClient.cs" />
|
||||
<Compile Include="Comm\GenericSecureTcpIpServer.cs" />
|
||||
<Compile Include="Comm\GenericSecureTcpIpClient_ForServer.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Comm\eControlMethods.cs" />
|
||||
<Compile Include="Comm\FINISH CommStatic.cs" />
|
||||
<Compile Include="Comm\CommunicationGather.cs" />
|
||||
<Compile Include="Comm\EventArgs.cs" />
|
||||
<Compile Include="Comm\GenericSshClient.cs" />
|
||||
<Compile Include="Comm\GenericUdpServer.cs" />
|
||||
<Compile Include="Comm\QscCoreDoubleTcpIpClient.cs" />
|
||||
<Compile Include="Comm\TcpClientConfigObject.cs" />
|
||||
<Compile Include="Comm\TcpServerConfigObject.cs" />
|
||||
<Compile Include="Config\PortalConfigReader.cs" />
|
||||
<Compile Include="CoreInterfaces.cs" />
|
||||
<Compile Include="Web\RequestHandlers\DefaultRequestHandler.cs" />
|
||||
<Compile Include="Web\RequestHandlers\WebApiBaseRequestHandler.cs" />
|
||||
<Compile Include="Web\WebApiServer.cs" />
|
||||
<Compile Include="EventArgs.cs" />
|
||||
<Compile Include="GenericRESTfulCommunications\Constants.cs" />
|
||||
<Compile Include="GenericRESTfulCommunications\GenericRESTfulClient.cs" />
|
||||
<Compile Include="JsonStandardObjects\EventArgs and Constants.cs" />
|
||||
<Compile Include="JsonStandardObjects\JsonToSimplDeviceConfig.cs" />
|
||||
<Compile Include="JsonStandardObjects\JsonToSimplDevice.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplPortalFileMaster.cs" />
|
||||
<Compile Include="Logging\Debug.cs" />
|
||||
<Compile Include="Logging\DebugContext.cs" />
|
||||
<Compile Include="Logging\DebugMemory.cs" />
|
||||
<Compile Include="Device.cs" />
|
||||
<Compile Include="Comm\GenericTcpIpServer.cs" />
|
||||
<Compile Include="EthernetHelper.cs" />
|
||||
<Compile Include="Comm\GenericTcpIpClient.cs" />
|
||||
<Compile Include="JsonToSimpl\Constants.cs" />
|
||||
<Compile Include="JsonToSimpl\Global.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplArrayLookupChild.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplChildObjectBase.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplFileMaster.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplFixedPathObject.cs" />
|
||||
<Compile Include="JsonToSimpl\REMOVE JsonToSimplFixedPathObject.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplGenericMaster.cs" />
|
||||
<Compile Include="JsonToSimpl\JsonToSimplMaster.cs" />
|
||||
<Compile Include="Network\DiscoveryThings.cs" />
|
||||
<Compile Include="PasswordManagement\Config.cs" />
|
||||
<Compile Include="PasswordManagement\Constants.cs" />
|
||||
<Compile Include="PasswordManagement\PasswordClient.cs" />
|
||||
<Compile Include="PasswordManagement\PasswordManager.cs" />
|
||||
<Compile Include="SystemInfo\EventArgs and Constants.cs" />
|
||||
<Compile Include="SystemInfo\SystemInfoConfig.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SystemInfo\SystemInfoToSimpl.cs" />
|
||||
<Compile Include="WebApi\Presets\Preset.cs" />
|
||||
<Compile Include="WebApi\Presets\User.cs" />
|
||||
<Compile Include="WebApi\Presets\WebApiPasscodeClient.cs" />
|
||||
<Compile Include="XSigUtility\Serialization\IXSigSerialization.cs" />
|
||||
<Compile Include="XSigUtility\Serialization\XSigSerializationException.cs" />
|
||||
<Compile Include="XSigUtility\Tokens\XSigAnalogToken.cs" />
|
||||
<Compile Include="XSigUtility\Tokens\XSigDigitalToken.cs" />
|
||||
<Compile Include="XSigUtility\Tokens\XSigSerialToken.cs" />
|
||||
<Compile Include="XSigUtility\Tokens\XSigToken.cs" />
|
||||
<Compile Include="XSigUtility\Tokens\XSigTokenType.cs" />
|
||||
<Compile Include="XSigUtility\XSigHelpers.cs" />
|
||||
<Compile Include="XSigUtility\XSigTokenStreamReader.cs" />
|
||||
<Compile Include="XSigUtility\XSigTokenStreamWriter.cs" />
|
||||
<None Include="Properties\ControlSystem.cfg" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CompactFramework.CSharp.targets" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>rem S# preparation will execute after these operations</PostBuildEvent>
|
||||
<PreBuildEvent>del "$(TargetDir)PepperDash_Core.*" /q
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=CrestronWebServer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=Web/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>PepperDashCore</id>
|
||||
<version>1.0.37</version>
|
||||
<title>PepperDash Core</title>
|
||||
<authors>PepperDash Technologies</authors>
|
||||
<owners>pepperdash</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/PepperDash/PepperDashCore</projectUrl>
|
||||
<copyright>Copyright 2020</copyright>
|
||||
<description>PepperDash Core is an open source Crestron SIMPL# library that can be used in SIMPL# Pro applications such as Essentials or as a standalone library with SIMPL+ wrappers to expose functionality in SIMPL Windows programs.</description>
|
||||
<tags>crestron 3series 4series</tags>
|
||||
<repository type="git" url="https://github.com/PepperDash/PepperDashCore"/>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="**" target="lib\net35"/>
|
||||
<file src="**" target="lib\net47"/>
|
||||
</files>
|
||||
</package>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[assembly: System.Reflection.AssemblyTitle("Pepperdash_Core")]
|
||||
[assembly: System.Reflection.AssemblyCompany("PepperDash Technology Corp")]
|
||||
[assembly: System.Reflection.AssemblyProduct("Pepperdash_Core")]
|
||||
[assembly: System.Reflection.AssemblyCopyright("Copyright © PepperDash 2019")]
|
||||
[assembly: System.Reflection.AssemblyVersion("1.0.0.*")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersion("0.0.0-buildType-buildNumber")]
|
||||
[assembly: Crestron.SimplSharp.Reflection.AssemblyInformationalVersion("0.0.0-buildType-buildNumber")]
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyTitle("Pepperdash_Core")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Pepperdash_Core")]
|
||||
[assembly: AssemblyCopyright("Copyright © PepperDash 2016")]
|
||||
<<<<<<< HEAD
|
||||
[assembly: AssemblyVersion("1.0.2.*")]
|
||||
=======
|
||||
[assembly: AssemblyVersion("1.0.3.*")]
|
||||
>>>>>>> e16c2cbee87c631bf84ad9634bfbc25bbec78a31
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
function Update-SourceVersion
|
||||
{
|
||||
Param ([string]$Version)
|
||||
$NewVersion = ‘AssemblyVersion("‘ + $Version + ‘.*")’;
|
||||
foreach ($o in $input)
|
||||
{
|
||||
Write-output $o.FullName
|
||||
$TmpFile = $o.FullName + “.tmp”
|
||||
get-content $o.FullName |
|
||||
%{$_ -replace ‘AssemblyVersion\("(\d+\.\d+\.\d+)\.\*"\)’, $NewVersion } > $TmpFile
|
||||
move-item $TmpFile $o.FullName -force
|
||||
}
|
||||
}
|
||||
|
||||
function Update-AllAssemblyInfoFiles ( $version )
|
||||
{
|
||||
foreach ($file in “AssemblyInfo.cs”, “AssemblyInfo.vb” )
|
||||
{
|
||||
get-childitem -recurse |? {$_.Name -eq $file} | Update-SourceVersion $version ;
|
||||
}
|
||||
}
|
||||
|
||||
# validate arguments
|
||||
$r= [System.Text.RegularExpressions.Regex]::Match($args[0], "^\d+\.\d+\.\d+$");
|
||||
if ($r.Success)
|
||||
{
|
||||
echo "Updating Assembly Version...";
|
||||
Update-AllAssemblyInfoFiles $args[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
echo ” “;
|
||||
echo “Error: Input version does not match x.y.z format!”
|
||||
echo ” “;
|
||||
echo "Unable to apply version to AssemblyInfo.cs files";
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -15,38 +13,44 @@ namespace PepperDash.Core
|
|||
/// <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(NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public uint ControlPortNumber { get; set; }
|
||||
[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(NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public string ControlPortName { get; set; }
|
||||
|
||||
/// <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>
|
||||
|
|
@ -58,21 +62,25 @@ namespace PepperDash.Core
|
|||
/// <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>
|
||||
|
|
@ -80,7 +88,6 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
public ControlPropertiesConfig()
|
||||
{
|
||||
EndOfLineString = CrestronEnvironment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,6 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -1,148 +1,151 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Crestron.SimplSharp.Ssh;
|
||||
using Crestron.SimplSharp.Ssh.Common;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using PepperDash.Core.Logging;
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SPlusKey = "Uninitialized SshClient";
|
||||
{
|
||||
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. 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 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>
|
||||
/// 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>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for server
|
||||
/// </summary>
|
||||
public string Username { 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>
|
||||
/// 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
|
||||
/// <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>
|
||||
/// 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>
|
||||
///
|
||||
/// </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>
|
||||
/// 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>
|
||||
/// 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>
|
||||
/// 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>
|
||||
/// 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; }
|
||||
/// <summary>
|
||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
||||
/// Set to 5000 by default
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
SshClient Client;
|
||||
SshClient Client;
|
||||
|
||||
ShellStream TheStream;
|
||||
ShellStream TheStream;
|
||||
|
||||
CTimer ReconnectTimer;
|
||||
CTimer ReconnectTimer;
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
private CCriticalSection connectLock = new CCriticalSection();
|
||||
//private CCriticalSection connectLock = new CCriticalSection();
|
||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
|
||||
private bool DisconnectLogged = false;
|
||||
|
||||
/// <summary>
|
||||
/// Typical constructor.
|
||||
/// </summary>
|
||||
public GenericSshClient(string key, string hostname, int port, string username, string password) :
|
||||
base(key)
|
||||
{
|
||||
/// <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;
|
||||
|
|
@ -158,7 +161,7 @@ namespace PepperDash.Core
|
|||
{
|
||||
Connect();
|
||||
}
|
||||
}, Timeout.Infinite);
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -176,15 +179,7 @@ namespace PepperDash.Core
|
|||
{
|
||||
Connect();
|
||||
}
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -196,22 +191,22 @@ namespace PepperDash.Core
|
|||
{
|
||||
if (Client != null)
|
||||
{
|
||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to the server, using the provided properties.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
/// <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)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Connect failed. Check hostname, port, username and password are set or not null");
|
||||
this.LogError("Connect failed. Check hostname, port, username and password are set or not null");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -219,14 +214,14 @@ namespace PepperDash.Core
|
|||
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
connectLock.Wait();
|
||||
if (IsConnected)
|
||||
{
|
||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
||||
this.LogDebug("Connection already connected. Exiting Connect");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Attempting connect");
|
||||
this.LogDebug("Attempting connect");
|
||||
|
||||
// Cancel reconnect if running.
|
||||
if (ReconnectTimer != null)
|
||||
|
|
@ -237,7 +232,7 @@ namespace PepperDash.Core
|
|||
// Cleanup the old client if it already exists
|
||||
if (Client != null)
|
||||
{
|
||||
Debug.Console(1, this, "Cleaning up disconnected client");
|
||||
this.LogDebug("Cleaning up disconnected client");
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
|
||||
|
|
@ -246,7 +241,7 @@ namespace PepperDash.Core
|
|||
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
|
||||
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
|
||||
|
||||
Debug.Console(1, this, "Creating new SshClient");
|
||||
this.LogDebug("Creating new SshClient");
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
||||
Client = new SshClient(connectionInfo);
|
||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
||||
|
|
@ -257,8 +252,13 @@ namespace PepperDash.Core
|
|||
{
|
||||
Client.Connect();
|
||||
TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534);
|
||||
if (TheStream.DataAvailable)
|
||||
{
|
||||
// empty the buffer if there is data
|
||||
string str = TheStream.Read();
|
||||
}
|
||||
TheStream.DataReceived += Stream_DataReceived;
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected");
|
||||
this.LogInformation("Connected");
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||
DisconnectLogged = false;
|
||||
}
|
||||
|
|
@ -268,35 +268,52 @@ namespace PepperDash.Core
|
|||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
|
||||
if (ie is SocketException)
|
||||
Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message);
|
||||
else if (ie is System.Net.Sockets.SocketException)
|
||||
Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})",
|
||||
Key, Hostname, Port, ie.GetType());
|
||||
else if (ie is SshAuthenticationException)
|
||||
{
|
||||
Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})",
|
||||
this.LogException(ie, "CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message);
|
||||
}
|
||||
|
||||
if (ie is System.Net.Sockets.SocketException socketException)
|
||||
{
|
||||
this.LogException(ie, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})",
|
||||
Key, Hostname, Port, ie.GetType());
|
||||
}
|
||||
if (ie is SshAuthenticationException)
|
||||
{
|
||||
this.LogException(ie, "Authentication failure for username '{0}', ({1})", this,
|
||||
Username, ie.Message);
|
||||
}
|
||||
else
|
||||
Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", ie.Message);
|
||||
this.LogException(ie, "Error on connect");
|
||||
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}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;
|
||||
Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e.Message);
|
||||
this.LogException(e, "Unhandled exception on connect");
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
|
|
@ -304,7 +321,7 @@ namespace PepperDash.Core
|
|||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
connectLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +339,7 @@ namespace PepperDash.Core
|
|||
}
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream, cleans up the client and sets it to null
|
||||
|
|
@ -340,43 +357,15 @@ namespace PepperDash.Core
|
|||
Client.Dispose();
|
||||
Client = null;
|
||||
ClientStatus = status;
|
||||
Debug.Console(1, this, "Disconnected");
|
||||
this.LogDebug("Disconnected");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Client:{0}", ex);
|
||||
this.LogException(ex,"Exception in Kill Client");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anything to do with reestablishing connection on failures
|
||||
/// </summary>
|
||||
void HandleConnectionFailure()
|
||||
{
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
|
||||
Debug.Console(1, this, "Client nulled due to connection failure. AutoReconnect: {0}, ConnectEnabled: {1}", AutoReconnect, ConnectEnabled);
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
if (ReconnectTimer == null)
|
||||
{
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
Connect();
|
||||
}, AutoReconnectIntervalMs);
|
||||
Debug.Console(1, this, "Attempting connection in {0} seconds",
|
||||
(float) (AutoReconnectIntervalMs/1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "{0} second reconnect cycle running",
|
||||
(float) (AutoReconnectIntervalMs/1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
|
|
@ -390,12 +379,12 @@ namespace PepperDash.Core
|
|||
TheStream.Close();
|
||||
TheStream.Dispose();
|
||||
TheStream = null;
|
||||
Debug.Console(1, this, "Disconnected stream");
|
||||
this.LogDebug("Disconnected stream");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Stream:{0}", ex);
|
||||
this.LogException(ex, "Exception in Kill Stream:{0}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,31 +401,35 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||
/// </summary>
|
||||
void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e)
|
||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
{
|
||||
var bytes = e.Data;
|
||||
if (bytes.Length > 0)
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
||||
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(str));
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||
}
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -444,42 +437,41 @@ namespace PepperDash.Core
|
|||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||
/// event
|
||||
/// </summary>
|
||||
void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e)
|
||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Disconnected by remote");
|
||||
this.LogError("Disconnected by remote");
|
||||
else
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception);
|
||||
|
||||
this.LogException(e.Exception, "Unhandled SSH client error");
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
connectLock.Wait();
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
connectLock.Release();
|
||||
}
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
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));
|
||||
}
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
{
|
||||
if (ConnectionChange != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
|
||||
#region IBasicCommunication Members
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// Sends text to the server
|
||||
|
|
@ -492,9 +484,8 @@ namespace PepperDash.Core
|
|||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0,
|
||||
this,
|
||||
"Sending {0} characters of text: '{1}'",
|
||||
this.LogInformation(
|
||||
"Sending {length} characters of text: '{text}'",
|
||||
text.Length,
|
||||
ComTextHelper.GetDebugText(text));
|
||||
|
||||
|
|
@ -503,23 +494,19 @@ namespace PepperDash.Core
|
|||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Client is null or disconnected. Cannot Send Text");
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Text");
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", text);
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
|
||||
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed");
|
||||
this.LogException(ex, "Exception sending text: {message}", text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -529,47 +516,43 @@ namespace PepperDash.Core
|
|||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
TheStream.Write(bytes, 0, bytes.Length);
|
||||
TheStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Client is null or disconnected. Cannot Send Bytes");
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Bytes");
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace);
|
||||
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed");
|
||||
}
|
||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************************************
|
||||
//*****************************************************************************************************
|
||||
/// <summary>
|
||||
/// Fired when connection changes
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
//*****************************************************************************************************
|
||||
//*****************************************************************************************************
|
||||
/// <summary>
|
||||
/// Fired when connection changes
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection State
|
||||
|
|
@ -591,10 +574,10 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
public ushort Status { get { return Client.UStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
|
|
@ -602,9 +585,9 @@ namespace PepperDash.Core
|
|||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -15,8 +15,6 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -6,7 +6,6 @@ using Crestron.SimplSharp;
|
|||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -27,6 +26,7 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// Indicates connection status
|
||||
/// </summary>
|
||||
[JsonProperty("isConnected")]
|
||||
bool IsConnected { get; }
|
||||
/// <summary>
|
||||
/// Connect to the device
|
||||
|
|
@ -72,6 +72,7 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
[JsonProperty("streamDebugging")]
|
||||
CommunicationStreamDebugging StreamDebugging { get; }
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +90,9 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// The current socket status of the client
|
||||
/// </summary>
|
||||
SocketStatus ClientStatus { get; }
|
||||
[JsonProperty("clientStatus")]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -108,10 +111,12 @@ namespace PepperDash.Core
|
|||
/// <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; }
|
||||
}
|
||||
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Core.Config
|
||||
|
|
@ -83,7 +79,7 @@ namespace PepperDash.Core.Config
|
|||
merged.Add("info", template["info"]);
|
||||
|
||||
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
|
||||
system["devices"] as JArray, "uid", "devices"));
|
||||
system["devices"] as JArray, "key", "devices"));
|
||||
|
||||
if (system["rooms"] == null)
|
||||
merged.Add("rooms", template["rooms"]);
|
||||
|
|
@ -102,9 +98,22 @@ namespace PepperDash.Core.Config
|
|||
merged.Add("destinationLists",
|
||||
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
|
||||
|
||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
||||
// level anyway...
|
||||
if (template["tieLines"] != null)
|
||||
|
||||
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"]);
|
||||
|
|
@ -121,7 +130,7 @@ namespace PepperDash.Core.Config
|
|||
else
|
||||
merged.Add("global", template["global"]);
|
||||
|
||||
Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -14,17 +16,20 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
[JsonProperty("key")]
|
||||
string Key { get; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Named Keyed device interface. Forces the devie to have a Unique Key and a name.
|
||||
/// 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,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -10,6 +10,7 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
public class Device : IKeyName
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
|
|
@ -49,9 +50,8 @@ namespace PepperDash.Core
|
|||
public Device(string key)
|
||||
{
|
||||
Key = key;
|
||||
if (key.Contains('.')) Debug.Console(0, this, "WARNING: Device name's should not include '.'");
|
||||
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
|
||||
Name = "";
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -99,7 +99,15 @@ namespace PepperDash.Core
|
|||
public void PreActivate()
|
||||
{
|
||||
if (_PreActivationActions != null)
|
||||
_PreActivationActions.ForEach(a => a.Invoke());
|
||||
_PreActivationActions.ForEach(a => {
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
} catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,7 +131,16 @@ namespace PepperDash.Core
|
|||
public void PostActivate()
|
||||
{
|
||||
if (_PostActivationActions != null)
|
||||
_PostActivationActions.ForEach(a => a.Invoke());
|
||||
_PostActivationActions.ForEach(a => {
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
19
src/Directory.build.props
Normal file
19
src/Directory.build.props
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<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>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<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>
|
||||
43
src/Directory.build.targets
Normal file
43
src/Directory.build.targets
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<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>
|
||||
21
src/EssentialsPlugins-builds-4-series-caller.yml
Normal file
21
src/EssentialsPlugins-builds-4-series-caller.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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,9 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core.JsonToSimpl;
|
||||
|
||||
namespace PepperDash.Core.JsonStandardObjects
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
//using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl
|
||||
|
|
@ -196,7 +194,7 @@ namespace PepperDash.Core.JsonToSimpl
|
|||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public void setDebugLevel(int level)
|
||||
public void setDebugLevel(uint level)
|
||||
{
|
||||
Debug.SetDebugLevel(level);
|
||||
}
|
||||
|
|
@ -1,12 +1,4 @@
|
|||
using System;
|
||||
//using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl
|
||||
{
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
//using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -162,7 +160,11 @@ namespace PepperDash.Core.JsonToSimpl
|
|||
/// <returns></returns>
|
||||
public static JObject ParseObject(string json)
|
||||
{
|
||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||
#if NET6_0
|
||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||
#else
|
||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||
#endif
|
||||
{
|
||||
var startDepth = reader.Depth;
|
||||
var obj = JObject.Load(reader);
|
||||
|
|
@ -179,7 +181,11 @@ namespace PepperDash.Core.JsonToSimpl
|
|||
/// <returns></returns>
|
||||
public static JArray ParseArray(string json)
|
||||
{
|
||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||
#if NET6_0
|
||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||
#else
|
||||
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||
#endif
|
||||
{
|
||||
var startDepth = reader.Depth;
|
||||
var obj = JArray.Load(reader);
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
using System;
|
||||
//using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PepperDash.Core.Config;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl
|
||||
|
|
@ -132,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl
|
|||
///
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public void setDebugLevel(int level)
|
||||
public void setDebugLevel(uint level)
|
||||
{
|
||||
Debug.SetDebugLevel(level);
|
||||
}
|
||||
37
src/Logging/CrestronEnricher.cs
Normal file
37
src/Logging/CrestronEnricher.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using Crestron.SimplSharp.CrestronLogger;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronDataStore;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronLogger;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.DebugThings;
|
||||
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Serilog.Formatting.Json;
|
||||
using Serilog.Templates;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
|
|
@ -16,6 +23,43 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
public static class Debug
|
||||
{
|
||||
private static readonly string LevelStoreKey = "ConsoleDebugLevel";
|
||||
private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel";
|
||||
private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel";
|
||||
private static readonly string FileLevelStoreKey = "FileDebugLevel";
|
||||
|
||||
private static readonly Dictionary<uint, LogEventLevel> _logLevels = new Dictionary<uint, LogEventLevel>()
|
||||
{
|
||||
{0, LogEventLevel.Information },
|
||||
{3, LogEventLevel.Warning },
|
||||
{4, LogEventLevel.Error },
|
||||
{5, LogEventLevel.Fatal },
|
||||
{1, LogEventLevel.Debug },
|
||||
{2, LogEventLevel.Verbose },
|
||||
};
|
||||
|
||||
private static ILogger _logger;
|
||||
|
||||
private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch _fileLevelSwitch;
|
||||
|
||||
public static LogEventLevel WebsocketMinimumLogLevel
|
||||
{
|
||||
get { return _websocketLoggingLevelSwitch.MinimumLevel; }
|
||||
}
|
||||
|
||||
private static readonly DebugWebsocketSink _websocketSink;
|
||||
|
||||
public static DebugWebsocketSink WebsocketSink
|
||||
{
|
||||
get { return _websocketSink; }
|
||||
}
|
||||
|
||||
/// <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.
|
||||
|
|
@ -41,12 +85,14 @@ namespace PepperDash.Core
|
|||
/// <summary>
|
||||
/// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal
|
||||
/// </summary>
|
||||
public static bool DoNotLoadOnNextBoot { get; private set; }
|
||||
public static bool DoNotLoadConfigOnNextBoot { get; private set; }
|
||||
|
||||
private static DebugContextCollection _contexts;
|
||||
|
||||
private const int SaveTimeoutMs = 30000;
|
||||
|
||||
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
|
||||
|
||||
/// <summary>
|
||||
/// Version for the currently loaded PepperDashCore dll
|
||||
/// </summary>
|
||||
|
|
@ -64,29 +110,93 @@ namespace PepperDash.Core
|
|||
|
||||
private static readonly Dictionary<string, object> IncludedExcludedKeys;
|
||||
|
||||
private static readonly LoggerConfiguration _defaultLoggerConfiguration;
|
||||
|
||||
private static LoggerConfiguration _loggerConfiguration;
|
||||
|
||||
public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
|
||||
|
||||
static Debug()
|
||||
{
|
||||
CrestronDataStoreStatic.InitCrestronDataStore();
|
||||
|
||||
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
|
||||
|
||||
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
|
||||
|
||||
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
|
||||
|
||||
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
|
||||
|
||||
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
||||
|
||||
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
||||
|
||||
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||
|
||||
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||
|
||||
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
|
||||
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
|
||||
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
|
||||
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
|
||||
|
||||
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
|
||||
|
||||
var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
||||
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
|
||||
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
|
||||
|
||||
_defaultLoggerConfiguration = new LoggerConfiguration()
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With(new CrestronEnricher())
|
||||
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
|
||||
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
|
||||
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
|
||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
|
||||
levelSwitch: _fileLevelSwitch
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
if (InitialParametersClass.NumberOfRemovableDrives > 0)
|
||||
{
|
||||
CrestronConsole.PrintLine("{0} RM Drive(s) Present. Initializing CrestronLogger", InitialParametersClass.NumberOfRemovableDrives);
|
||||
_defaultLoggerConfiguration.WriteTo.Sink(new DebugCrestronLoggerSink());
|
||||
}
|
||||
else
|
||||
CrestronConsole.PrintLine("No RM Drive(s) Present. Not using Crestron Logger");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
|
||||
}
|
||||
|
||||
// Instantiate the root logger
|
||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||
|
||||
_logger = _loggerConfiguration.CreateLogger();
|
||||
// Get the assembly version and print it to console and the log
|
||||
GetVersion();
|
||||
|
||||
string msg = "";
|
||||
string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
|
||||
{
|
||||
msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion);
|
||||
}
|
||||
else if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
|
||||
{
|
||||
msg = string.Format("[Room {0}] Using PepperDash_Core v{1}", InitialParametersClass.RoomId, PepperDashCoreVersion);
|
||||
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||
}
|
||||
|
||||
CrestronConsole.PrintLine(msg);
|
||||
|
||||
LogError(ErrorLogLevel.Notice, msg);
|
||||
LogMessage(LogEventLevel.Information,msg);
|
||||
|
||||
IncludedExcludedKeys = new Dictionary<string, object>();
|
||||
|
||||
//CrestronDataStoreStatic.InitCrestronDataStore();
|
||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||
{
|
||||
// Add command to console
|
||||
|
|
@ -94,7 +204,7 @@ namespace PepperDash.Core
|
|||
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||
"appdebug:P [0-2]: Sets the application's console debug message level",
|
||||
"appdebug:P [0-5]: Sets the application's console debug message level",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
|
||||
"appdebuglog:P [all] Use \"all\" for full log.",
|
||||
|
|
@ -112,25 +222,54 @@ namespace PepperDash.Core
|
|||
|
||||
var context = _contexts.GetOrCreateItem("DEFAULT");
|
||||
Level = context.Level;
|
||||
DoNotLoadOnNextBoot = context.DoNotLoadOnNextBoot;
|
||||
DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot;
|
||||
|
||||
if(DoNotLoadOnNextBoot)
|
||||
if(DoNotLoadConfigOnNextBoot)
|
||||
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
|
||||
|
||||
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
||||
{
|
||||
Console(0, "Console debug level set to {0}", _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
};
|
||||
}
|
||||
|
||||
public static void UpdateLoggerConfiguration(LoggerConfiguration config)
|
||||
{
|
||||
_loggerConfiguration = config;
|
||||
|
||||
_logger = config.CreateLogger();
|
||||
}
|
||||
|
||||
public static void ResetLoggerConfiguration()
|
||||
{
|
||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||
|
||||
_logger = _loggerConfiguration.CreateLogger();
|
||||
}
|
||||
|
||||
private static LogEventLevel GetStoredLogEventLevel(string levelStoreKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (InitialParametersClass.NumberOfRemovableDrives > 0)
|
||||
{
|
||||
CrestronConsole.PrintLine("{0} RM Drive(s) Present.", InitialParametersClass.NumberOfRemovableDrives);
|
||||
CrestronLogger.Initialize(2, LoggerModeEnum.DEFAULT); // Use RM instead of DEFAULT as not to double-up console messages.
|
||||
}
|
||||
else
|
||||
CrestronConsole.PrintLine("No RM Drive(s) Present.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
|
||||
|
||||
CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
|
||||
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
{
|
||||
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
|
||||
if(logLevel < 0 || logLevel > 5)
|
||||
{
|
||||
CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
|
||||
return (LogEventLevel)logLevel;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,9 +282,7 @@ namespace PepperDash.Core
|
|||
|
||||
if (ver != null && ver.Length > 0)
|
||||
{
|
||||
var verAttribute = ver[0] as AssemblyInformationalVersionAttribute;
|
||||
|
||||
if (verAttribute != null)
|
||||
if (ver[0] is AssemblyInformationalVersionAttribute verAttribute)
|
||||
{
|
||||
PepperDashCoreVersion = verAttribute.InformationalVersion;
|
||||
}
|
||||
|
|
@ -153,8 +290,7 @@ namespace PepperDash.Core
|
|||
else
|
||||
{
|
||||
var version = assembly.GetName().Version;
|
||||
PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build,
|
||||
version.Revision);
|
||||
PepperDashCoreVersion = version.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +300,11 @@ namespace PepperDash.Core
|
|||
/// <param name="programEventType"></param>
|
||||
static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
|
||||
if (_saveTimer != null)
|
||||
{
|
||||
_saveTimer.Stop();
|
||||
|
|
@ -184,20 +323,121 @@ namespace PepperDash.Core
|
|||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
if (levelString.Trim() == "?")
|
||||
{
|
||||
CrestronConsole.PrintLine("AppDebug level = {0}", Level);
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
$@"Used to set the minimum level of debug messages to be printed to the console:
|
||||
{_logLevels[0]} = 0
|
||||
{_logLevels[1]} = 1
|
||||
{_logLevels[2]} = 2
|
||||
{_logLevels[3]} = 3
|
||||
{_logLevels[4]} = 4
|
||||
{_logLevels[5]} = 5");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDebugLevel(Convert.ToInt32(levelString));
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
if(int.TryParse(levelString, out var levelInt))
|
||||
{
|
||||
if(levelInt < 0 || levelInt > 5)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
|
||||
return;
|
||||
}
|
||||
SetDebugLevel((uint) levelInt);
|
||||
return;
|
||||
}
|
||||
|
||||
if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
|
||||
{
|
||||
SetDebugLevel(levelEnum);
|
||||
return;
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
|
||||
}
|
||||
catch
|
||||
{
|
||||
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"> Valid values 0-5</param>
|
||||
public static void SetDebugLevel(uint level)
|
||||
{
|
||||
if(!_logLevels.TryGetValue(level, out var logLevel))
|
||||
{
|
||||
logLevel = LogEventLevel.Information;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
||||
|
||||
SetDebugLevel(logLevel);
|
||||
}
|
||||
|
||||
SetDebugLevel(logLevel);
|
||||
}
|
||||
|
||||
public static void SetDebugLevel(LogEventLevel level)
|
||||
{
|
||||
_consoleLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
|
||||
InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}");
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int) level);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
|
||||
}
|
||||
|
||||
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
_websocketLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
_errorLogLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
public static void SetFileMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
_errorLogLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for console command
|
||||
/// </summary>
|
||||
|
|
@ -208,15 +448,15 @@ namespace PepperDash.Core
|
|||
{
|
||||
if (string.IsNullOrEmpty(stateString.Trim()))
|
||||
{
|
||||
CrestronConsole.PrintLine("DoNotLoadOnNextBoot = {0}", DoNotLoadOnNextBoot);
|
||||
CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
|
||||
return;
|
||||
}
|
||||
|
||||
SetDoNotLoadOnNextBoot(Boolean.Parse(stateString));
|
||||
SetDoNotLoadConfigOnNextBoot(bool.Parse(stateString));
|
||||
}
|
||||
catch
|
||||
{
|
||||
CrestronConsole.PrintLine("Usage: donotloadonnextboot:P [true/false]");
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +472,7 @@ namespace PepperDash.Core
|
|||
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
|
||||
"+all: at beginning puts filter into 'default include' mode\r" +
|
||||
" All keys that follow will be excluded from output.\r" +
|
||||
"-all: at beginning puts filter into 'default excluse all' mode.\r" +
|
||||
"-all: at beginning puts filter into 'default exclude all' mode.\r" +
|
||||
" All keys that follow will be the only keys that are shown\r" +
|
||||
"+nokey: Enables messages with no key (default)\r" +
|
||||
"-nokey: Disables messages with no key.\r" +
|
||||
|
|
@ -300,26 +540,7 @@ namespace PepperDash.Core
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
|
||||
public static void SetDebugLevel(int level)
|
||||
{
|
||||
if (level <= 2)
|
||||
{
|
||||
Level = level;
|
||||
_contexts.GetOrCreateItem("DEFAULT").Level = level;
|
||||
SaveMemoryOnTimeout();
|
||||
|
||||
CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
|
||||
InitialParametersClass.ApplicationNumber, Level);
|
||||
|
||||
//var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level);
|
||||
//if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
// CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sets the settings for a device or creates a new entry
|
||||
|
|
@ -347,14 +568,14 @@ namespace PepperDash.Core
|
|||
/// Sets the flag to prevent application starting on next boot
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public static void SetDoNotLoadOnNextBoot(bool state)
|
||||
public static void SetDoNotLoadConfigOnNextBoot(bool state)
|
||||
{
|
||||
DoNotLoadOnNextBoot = state;
|
||||
DoNotLoadConfigOnNextBoot = state;
|
||||
_contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state;
|
||||
SaveMemoryOnTimeout();
|
||||
|
||||
CrestronConsole.PrintLine("[Application {0}], Do Not Start on Next Boot set to {1}",
|
||||
InitialParametersClass.ApplicationNumber, DoNotLoadOnNextBoot);
|
||||
CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Load Config on Next Boot set to {1}",
|
||||
InitialParametersClass.ApplicationNumber, DoNotLoadConfigOnNextBoot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -367,6 +588,66 @@ namespace PepperDash.Core
|
|||
CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log an Exception using Serilog's default Exception logging mechanism
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception to log</param>
|
||||
/// <param name="message">Message template</param>
|
||||
/// <param name="device">Optional IKeyed device. If provided, the Key of the device will be added to the log message</param>
|
||||
/// <param name="args">Args to put into message template</param>
|
||||
public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", device?.Key))
|
||||
{
|
||||
_logger.Error(ex, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message
|
||||
/// </summary>
|
||||
/// <param name="level">Level to log at</param>
|
||||
/// <param name="message">Message template</param>
|
||||
/// <param name="device">Optional IKeyed device. If provided, the Key of the device will be added to the log message</param>
|
||||
/// <param name="args">Args to put into message template</param>
|
||||
public static void LogMessage(LogEventLevel level, string message, IKeyed device=null, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", device?.Key))
|
||||
{
|
||||
_logger.Write(level, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogMessage(LogEventLevel level, string message, params object[] args)
|
||||
{
|
||||
LogMessage(level, message, null, args);
|
||||
}
|
||||
|
||||
public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
LogMessage(level, message, keyed, args);
|
||||
}
|
||||
|
||||
|
||||
private static void LogMessage(uint level, string format, params object[] items)
|
||||
{
|
||||
if (!_logLevels.ContainsKey(level)) return;
|
||||
|
||||
var logLevel = _logLevels[level];
|
||||
|
||||
LogMessage(logLevel, format, items);
|
||||
}
|
||||
|
||||
private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items)
|
||||
{
|
||||
if (!_logLevels.ContainsKey(level)) return;
|
||||
|
||||
var logLevel = _logLevels[level];
|
||||
|
||||
LogMessage(logLevel, keyed, format, items);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Prints message to console if current debug level is equal to or higher than the level of this message.
|
||||
/// Uses CrestronConsole.PrintLine.
|
||||
|
|
@ -374,67 +655,52 @@ namespace PepperDash.Core
|
|||
/// <param name="level"></param>
|
||||
/// <param name="format">Console format string</param>
|
||||
/// <param name="items">Object parameters</param>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void Console(uint level, string format, params object[] items)
|
||||
{
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
|
||||
{
|
||||
var logString = string.Format("[level {0}] {1}", level, string.Format(format, items));
|
||||
|
||||
LogError(ErrorLogLevel.Notice, logString);
|
||||
return;
|
||||
}
|
||||
LogMessage(level, format, items);
|
||||
|
||||
if(Level < level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CrestronConsole.PrintLine("[{0}]App {1}:{2}", DateTime.Now.ToString("HH:mm:ss.fff"), InitialParametersClass.ApplicationNumber,
|
||||
string.Format(format, items));
|
||||
//if (IsRunningOnAppliance)
|
||||
//{
|
||||
// CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"),
|
||||
// InitialParametersClass.ApplicationNumber,
|
||||
// level,
|
||||
// string.Format(format, items));
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to Console when at-level, and all messages to error log, including device key
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void Console(uint level, IKeyed dev, string format, params object[] items)
|
||||
{
|
||||
if (Level >= level)
|
||||
Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
|
||||
LogMessage(level, dev, format, items);
|
||||
|
||||
//if (Level >= level)
|
||||
// Console(level, "[{0}] {1}", dev.Key, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log.
|
||||
/// Uses CrestronConsole.PrintLine.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
|
||||
string format, params object[] items)
|
||||
{
|
||||
var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
|
||||
if (errorLogLevel != ErrorLogLevel.None)
|
||||
{
|
||||
LogError(errorLogLevel, str);
|
||||
}
|
||||
if (Level >= level)
|
||||
{
|
||||
Console(level, str);
|
||||
}
|
||||
LogMessage(level, dev, format, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to Console when at-level, and all messages to error log
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Logs to Console when at-level, and all messages to error log
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void Console(uint level, ErrorLogLevel errorLogLevel,
|
||||
string format, params object[] items)
|
||||
{
|
||||
var str = string.Format(format, items);
|
||||
if (errorLogLevel != ErrorLogLevel.None)
|
||||
{
|
||||
LogError(errorLogLevel, str);
|
||||
}
|
||||
if (Level >= level)
|
||||
{
|
||||
Console(level, str);
|
||||
}
|
||||
LogMessage(level, format, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -442,12 +708,15 @@ namespace PepperDash.Core
|
|||
/// or above the level provided, then the output will be written to both console and the log. Otherwise
|
||||
/// it will only be written to the log.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void ConsoleWithLog(uint level, string format, params object[] items)
|
||||
{
|
||||
var str = string.Format(format, items);
|
||||
if (Level >= level)
|
||||
CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
|
||||
CrestronLogger.WriteToLog(str, level);
|
||||
LogMessage(level, format, items);
|
||||
|
||||
// var str = string.Format(format, items);
|
||||
//if (Level >= level)
|
||||
// CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
|
||||
// CrestronLogger.WriteToLog(str, level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -455,11 +724,13 @@ namespace PepperDash.Core
|
|||
/// or above the level provided, then the output will be written to both console and the log. Otherwise
|
||||
/// it will only be written to the log.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
|
||||
{
|
||||
var str = string.Format(format, items);
|
||||
if (Level >= level)
|
||||
ConsoleWithLog(level, "[{0}] {1}", dev.Key, str);
|
||||
LogMessage(level, dev, format, items);
|
||||
|
||||
// var str = string.Format(format, items);
|
||||
// CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -467,20 +738,19 @@ namespace PepperDash.Core
|
|||
/// </summary>
|
||||
/// <param name="errorLogLevel"></param>
|
||||
/// <param name="str"></param>
|
||||
[Obsolete("Use LogMessage methods")]
|
||||
public static void LogError(ErrorLogLevel errorLogLevel, string str)
|
||||
{
|
||||
|
||||
var msg = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str) : string.Format("Room {0}:{1}", InitialParametersClass.RoomId, str);
|
||||
switch (errorLogLevel)
|
||||
{
|
||||
case ErrorLogLevel.Error:
|
||||
ErrorLog.Error(msg);
|
||||
LogMessage(LogEventLevel.Error, str);
|
||||
break;
|
||||
case ErrorLogLevel.Warning:
|
||||
ErrorLog.Warn(msg);
|
||||
LogMessage(LogEventLevel.Warning, str);
|
||||
break;
|
||||
case ErrorLogLevel.Notice:
|
||||
ErrorLog.Notice(msg);
|
||||
LogMessage(LogEventLevel.Information, str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -553,7 +823,7 @@ namespace PepperDash.Core
|
|||
{
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
|
||||
{
|
||||
CheckForMigration();
|
||||
// CheckForMigration();
|
||||
return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
|
||||
}
|
||||
|
||||
55
src/Logging/DebugConsoleSink.cs
Normal file
55
src/Logging/DebugConsoleSink.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
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,12 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronDataStore;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.DebugThings;
|
||||
|
||||
|
||||
namespace PepperDash.Core
|
||||
|
|
@ -101,7 +98,7 @@ namespace PepperDash.Core
|
|||
{
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
{
|
||||
CrestronConsole.PrintLine("AppDebug level = {0}", SaveData.Level);
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
|
||||
return;
|
||||
}
|
||||
|
||||
29
src/Logging/DebugCrestronLoggerSink.cs
Normal file
29
src/Logging/DebugCrestronLoggerSink.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/Logging/DebugErrorLogSink.cs
Normal file
65
src/Logging/DebugErrorLogSink.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Logging/DebugExtensions.cs
Normal file
44
src/Logging/DebugExtensions.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Serilog;
|
||||
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, string message, params object[] args)
|
||||
{
|
||||
Log.LogMessage(LogEventLevel.Verbose, device, message, 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, string message, params object[] args)
|
||||
{
|
||||
Log.LogMessage(LogEventLevel.Information, device, message, 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, string message, params object[] args)
|
||||
{
|
||||
Log.LogMessage(LogEventLevel.Error, device, message, args);
|
||||
}
|
||||
|
||||
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
||||
{
|
||||
Log.LogMessage(LogEventLevel.Fatal, device, message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core.DebugThings
|
||||
namespace PepperDash.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to persist current Debug settings across program restarts
|
||||
271
src/Logging/DebugWebsocketSink.cs
Normal file
271
src/Logging/DebugWebsocketSink.cs
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
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.IdKPServerAuth, KeyPurposeID.IdKPClientAuth });
|
||||
//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,8 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement
|
||||
{
|
||||
|
|
@ -1,12 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core.JsonToSimpl;
|
||||
using PepperDash.Core.JsonStandardObjects;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement
|
||||
{
|
||||
63
src/PepperDash.Core.4Series.csproj
Normal file
63
src/PepperDash.Core.4Series.csproj
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<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>
|
||||
356
src/Web/BouncyCertificate.cs
Normal file
356
src/Web/BouncyCertificate.cs
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
src/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs
Normal file
163
src/Web/RequestHandlers/WebApiBaseRequestAsyncHandler.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
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,11 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace PepperDash.Core.WebApi.Presets
|
||||
{
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp; // For Basic SIMPL# Classes
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.Net;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using Crestron.SimplSharp.Net.Https;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.JsonToSimpl;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue