Merge pull request #188 from PepperDash/release

Release PD Core 2.0.0
This commit is contained in:
Andrew Welker 2025-03-06 09:52:56 -06:00 committed by GitHub
commit ab61b2673a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
105 changed files with 12503 additions and 13387 deletions

Binary file not shown.

View file

@ -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

View file

@ -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";
}

View file

@ -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

View 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 }}

View file

@ -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

View file

@ -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
View file

@ -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
View 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"
}
]
}

View 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

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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()
// {
// }
// }
//}

View file

@ -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; }
//}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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")]

View file

@ -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

View file

@ -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";
}

View file

@ -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;
}
}
}

View file

@ -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
{

View file

@ -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;
}
}
}

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

View file

@ -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
{

View file

@ -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
{

View file

@ -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; }
}

View file

@ -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;
}

View file

@ -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; }
}
}
}

View file

@ -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
View 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>

View 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>

View 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 }}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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
{

View file

@ -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);

View file

@ -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);
}

View 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);
}
}
}

View file

@ -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);
}

View 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));
}
}
}

View file

@ -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;
}

View 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);
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View file

@ -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

View 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);
}
}
}

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Core.PasswordManagement
{

View file

@ -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
{

View 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>

View 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;
}
}
}

View 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();
}
}
}

View file

@ -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
{

View file

@ -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