mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-04-09 14:34:55 +00:00
Compare commits
10 Commits
dev/v3
...
v3.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ed5e648c0 | ||
|
|
35d44f91e4 | ||
|
|
fa9dc3a2b1 | ||
|
|
7e3c62b303 | ||
|
|
884b2c2e6e | ||
|
|
daf9b4bda0 | ||
|
|
e818c9ca03 | ||
|
|
5dd6d18fcc | ||
|
|
37961b9eac | ||
|
|
24df4e7a03 |
@@ -6,9 +6,13 @@ on:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
runTests:
|
||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-run-tests.yml@main
|
||||
secrets: inherit
|
||||
getVersion:
|
||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
||||
secrets: inherit
|
||||
needs: runTests
|
||||
build-4Series:
|
||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
||||
secrets: inherit
|
||||
@@ -19,4 +23,5 @@ jobs:
|
||||
version: ${{ needs.getVersion.outputs.version }}
|
||||
tag: ${{ needs.getVersion.outputs.tag }}
|
||||
channel: ${{ needs.getVersion.outputs.channel }}
|
||||
bypassPackageCheck: true
|
||||
bypassPackageCheck: true
|
||||
devToolsVersion: ${{ vars.ESSENTIALSDEVTOOLSVERSION }}
|
||||
247
.github/workflows/essentials-3-dev-build.yml
vendored
247
.github/workflows/essentials-3-dev-build.yml
vendored
@@ -1,247 +0,0 @@
|
||||
name: Essentials v3 Development Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feature-3.0.0/*
|
||||
- hotfix-3.0.0/*
|
||||
- release-3.0.0/*
|
||||
- development-3.0.0
|
||||
|
||||
env:
|
||||
SOLUTION_PATH: .
|
||||
SOLUTION_FILE: PepperDash.Essentials
|
||||
VERSION: 0.0.0-buildtype-buildnumber
|
||||
BUILD_TYPE: Debug
|
||||
RELEASE_BRANCH: main
|
||||
jobs:
|
||||
Build_Project_4-Series:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Detect environment (Act vs GitHub)
|
||||
- name: Detect environment
|
||||
id: detect_env
|
||||
run: |
|
||||
if [ -n "$ACT" ]; then
|
||||
echo "is_local=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_local=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
if [ "${{ steps.detect_env.outputs.is_local }}" == "true" ]; then
|
||||
# For Act - no sudo needed
|
||||
apt-get update
|
||||
apt-get install -y curl wget libicu-dev git unzip
|
||||
else
|
||||
# For GitHub runners - sudo required
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl wget libicu-dev git unzip
|
||||
fi
|
||||
|
||||
- name: Set Version Number
|
||||
id: setVersion
|
||||
shell: bash
|
||||
run: |
|
||||
latestVersion="3.0.0"
|
||||
newVersion=$latestVersion
|
||||
phase=""
|
||||
newVersionString=""
|
||||
|
||||
if [[ $GITHUB_REF =~ ^refs/pull/.* ]]; then
|
||||
phase="beta"
|
||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
||||
elif [[ $GITHUB_REF =~ ^refs/heads/hotfix-3.0.0/.* ]]; then
|
||||
phase="hotfix"
|
||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
||||
elif [[ $GITHUB_REF =~ ^refs/heads/feature-3.0.0/.* ]]; then
|
||||
phase="alpha"
|
||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
||||
elif [[ $GITHUB_REF == "refs/heads/development-3.0.0" ]]; then
|
||||
phase="beta"
|
||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
||||
elif [[ $GITHUB_REF =~ ^refs/heads/release-3.0.0/.* ]]; then
|
||||
version=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | sed 's/v//')
|
||||
phase="rc"
|
||||
newVersionString="${version}-${phase}-${GITHUB_RUN_NUMBER}"
|
||||
else
|
||||
# For local builds or unrecognized branches
|
||||
newVersionString="${newVersion}-local"
|
||||
fi
|
||||
|
||||
echo "version=$newVersionString" >> $GITHUB_OUTPUT
|
||||
|
||||
# Create Build Properties file
|
||||
- name: Create Build Properties
|
||||
run: |
|
||||
cat > Directory.Build.props << EOF
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>${{ steps.setVersion.outputs.version }}</Version>
|
||||
<AssemblyVersion>${{ steps.setVersion.outputs.version }}</AssemblyVersion>
|
||||
<FileVersion>${{ steps.setVersion.outputs.version }}</FileVersion>
|
||||
<InformationalVersion>${{ steps.setVersion.outputs.version }}</InformationalVersion>
|
||||
<PackageVersion>${{ steps.setVersion.outputs.version }}</PackageVersion>
|
||||
<NuGetVersion>${{ steps.setVersion.outputs.version }}</NuGetVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
EOF
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: dotnet restore ${SOLUTION_FILE}.sln
|
||||
|
||||
- name: Build Solution
|
||||
run: dotnet build ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --no-restore
|
||||
|
||||
# Copy the CPZ file to the output directory with version in the filename
|
||||
- name: Copy and Rename CPZ Files
|
||||
run: |
|
||||
mkdir -p ./output/cpz
|
||||
|
||||
# Find the main CPZ file in the build output
|
||||
if [ -f "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" ]; then
|
||||
cp "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" "./output/cpz/PepperDashEssentials.${{ steps.setVersion.outputs.version }}.cpz"
|
||||
echo "Main CPZ file copied and renamed successfully."
|
||||
else
|
||||
echo "Warning: Main CPZ file not found at expected location."
|
||||
find ./src -name "*.cpz" | xargs -I {} cp {} ./output/cpz/
|
||||
fi
|
||||
|
||||
- name: Pack Solution
|
||||
run: dotnet pack ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --output ./output/nuget --no-build
|
||||
|
||||
# List build artifacts (runs in both environments)
|
||||
- name: List Build Artifacts
|
||||
run: |
|
||||
echo "=== Build Artifacts ==="
|
||||
echo "NuGet Packages:"
|
||||
find ./output/nuget -type f | sort
|
||||
echo ""
|
||||
echo "CPZ/CPLZ Files:"
|
||||
find ./output -name "*.cpz" -o -name "*.cplz" | sort
|
||||
echo "======================="
|
||||
|
||||
# Enhanced package inspection for local runs
|
||||
- name: Inspect NuGet Packages
|
||||
if: steps.detect_env.outputs.is_local == 'true'
|
||||
run: |
|
||||
echo "=== NuGet Package Details ==="
|
||||
for pkg in $(find ./output/nuget -name "*.nupkg"); do
|
||||
echo "Package: $(basename "$pkg")"
|
||||
echo "Size: $(du -h "$pkg" | cut -f1)"
|
||||
|
||||
# Extract and show package contents
|
||||
echo "Contents:"
|
||||
unzip -l "$pkg" | tail -n +4 | head -n -2
|
||||
echo "--------------------------"
|
||||
|
||||
# Try to extract and show the nuspec file (contains metadata)
|
||||
echo "Metadata:"
|
||||
unzip -p "$pkg" "*.nuspec" 2>/dev/null | grep -E "(<id>|<version>|<description>|<authors>|<dependencies>)" || echo "Metadata extraction failed"
|
||||
echo "--------------------------"
|
||||
done
|
||||
echo "==========================="
|
||||
|
||||
# Tag creation - GitHub version
|
||||
- name: Create tag for non-rc builds (GitHub)
|
||||
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'false' }}
|
||||
run: |
|
||||
git config --global user.name "GitHub Actions"
|
||||
git config --global user.email "actions@github.com"
|
||||
git tag ${{ steps.setVersion.outputs.version }}
|
||||
git push --tags origin
|
||||
|
||||
# Tag creation - Act mock version
|
||||
- name: Create tag for non-rc builds (Act Mock)
|
||||
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'true' }}
|
||||
run: |
|
||||
echo "Would create git tag: ${{ steps.setVersion.outputs.version }}"
|
||||
echo "Would push tag to: origin"
|
||||
|
||||
# Release creation - GitHub version
|
||||
- name: Create Release (GitHub)
|
||||
if: steps.detect_env.outputs.is_local == 'false'
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: 'output/cpz/*,output/**/*.cplz'
|
||||
generateReleaseNotes: true
|
||||
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
|
||||
tag: ${{ steps.setVersion.outputs.version }}
|
||||
|
||||
# Release creation - Act mock version with enhanced output
|
||||
- name: Create Release (Act Mock)
|
||||
if: steps.detect_env.outputs.is_local == 'true'
|
||||
run: |
|
||||
echo "=== Mock Release Creation ==="
|
||||
echo "Would create release with:"
|
||||
echo "- Tag: ${{ steps.setVersion.outputs.version }}"
|
||||
echo "- Prerelease: ${{contains('debug', env.BUILD_TYPE)}}"
|
||||
echo "- Artifacts matching pattern: output/cpz/*,output/**/*.cplz"
|
||||
echo ""
|
||||
echo "Matching artifacts:"
|
||||
find ./output/cpz -type f
|
||||
find ./output -name "*.cplz"
|
||||
|
||||
# Detailed info about release artifacts
|
||||
echo ""
|
||||
echo "Artifact Details:"
|
||||
for artifact in $(find ./output/cpz -type f; find ./output -name "*.cplz"); do
|
||||
echo "File: $(basename "$artifact")"
|
||||
echo "Size: $(du -h "$artifact" | cut -f1)"
|
||||
echo "Created: $(stat -c %y "$artifact")"
|
||||
echo "MD5: $(md5sum "$artifact" | cut -d' ' -f1)"
|
||||
echo "--------------------------"
|
||||
done
|
||||
echo "============================"
|
||||
|
||||
# NuGet setup - GitHub version
|
||||
- name: Setup NuGet (GitHub)
|
||||
if: steps.detect_env.outputs.is_local == 'false'
|
||||
run: |
|
||||
dotnet nuget add source https://nuget.pkg.github.com/pepperdash/index.json -n github -u pepperdash -p ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
|
||||
|
||||
# NuGet setup - Act mock version
|
||||
- name: Setup NuGet (Act Mock)
|
||||
if: steps.detect_env.outputs.is_local == 'true'
|
||||
run: |
|
||||
echo "=== Mock NuGet Setup ==="
|
||||
echo "Would add GitHub NuGet source: https://nuget.pkg.github.com/pepperdash/index.json"
|
||||
echo "======================="
|
||||
|
||||
# Publish to NuGet - GitHub version
|
||||
- name: Publish to Nuget (GitHub)
|
||||
if: steps.detect_env.outputs.is_local == 'false'
|
||||
run: dotnet nuget push ./output/nuget/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
|
||||
|
||||
# Publish to NuGet - Act mock version
|
||||
- name: Publish to Nuget (Act Mock)
|
||||
if: steps.detect_env.outputs.is_local == 'true'
|
||||
run: |
|
||||
echo "=== Mock Publish to NuGet ==="
|
||||
echo "Would publish the following packages to https://api.nuget.org/v3/index.json:"
|
||||
find ./output/nuget -name "*.nupkg" | sort
|
||||
echo "============================="
|
||||
|
||||
# Publish to GitHub NuGet - GitHub version
|
||||
- name: Publish to Github Nuget (GitHub)
|
||||
if: steps.detect_env.outputs.is_local == 'false'
|
||||
run: dotnet nuget push ./output/nuget/*.nupkg --source github --api-key ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Publish to GitHub NuGet - Act mock version
|
||||
- name: Publish to Github Nuget (Act Mock)
|
||||
if: steps.detect_env.outputs.is_local == 'true'
|
||||
run: |
|
||||
echo "=== Mock Publish to GitHub NuGet ==="
|
||||
echo "Would publish the following packages to the GitHub NuGet registry:"
|
||||
find ./output/nuget -name "*.nupkg" | sort
|
||||
echo "=================================="
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -26,9 +26,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Dotnet Setup
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 8.x
|
||||
|
||||
|
||||
@@ -36,49 +36,151 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core.Tests", "src\PepperDash.Core.Tests\PepperDash.Core.Tests.csproj", "{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.Core.Tests", "src\PepperDash.Essentials.Core.Tests\PepperDash.Essentials.Core.Tests.csproj", "{F508E0BA-E885-424F-9D4C-359CF0011DEF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
||||
Debug 4.7.2|x64 = Debug 4.7.2|x64
|
||||
Debug 4.7.2|x86 = Debug 4.7.2|x86
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x64.Build.0 = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|x86.Build.0 = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x64.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x64.Build.0 = Debug 4.7.2|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x86.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|x86.Build.0 = Debug 4.7.2|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|x86.Build.0 = Release|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x64.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug 4.7.2|x86.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -90,6 +192,8 @@ Global
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{680BA287-E61F-4B8D-BD7A-84C2504F5F9C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{F508E0BA-E885-424F-9D4C-359CF0011DEF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||
|
||||
@@ -32,7 +32,7 @@ If the `CustomActivate()` method is long, consider breaking it up into many smal
|
||||
Note: It is best-practice in Essentials to not write arbitrarily-timed startup sequences to ensure that a "system" or room is functional. Rather, we encourage the developer to use various properties and conditions on devices to aggregate together "room is ready" statuses that can trigger further action. This ensures that all devices can be up and alive, allowing them to be debugged within a program that may otherwise be misbehaving - as well as not making users and expensive developers wait for code to start up!
|
||||
|
||||
```cs
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks");
|
||||
SetupFunctions();
|
||||
@@ -52,7 +52,7 @@ We can see in the example below that during the `CustomActivate()` phase, we def
|
||||
### **Example**
|
||||
|
||||
```cs
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
foreach (var i in Config.Inputs)
|
||||
{
|
||||
@@ -115,7 +115,7 @@ The main task that should be undertaken in the `Initialize()` method for any 3rd
|
||||
|
||||
### Example (from `PepperDash.Essentials.Devices.Common.VideoCodec.Cisco.CiscoSparkCodec`)
|
||||
```cs
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
var socket = Communication as ISocketStatus;
|
||||
if (socket != null)
|
||||
|
||||
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Allows pre-registration of Crestron service implementations before the <c>Debug</c>
|
||||
/// static class initialises. Call <see cref="Register"/> from the composition root
|
||||
/// (e.g. ControlSystem constructor) <em>before</em> any code touches <c>Debug.*</c>.
|
||||
/// Test projects should call it with no-op / in-memory implementations so that the
|
||||
/// <c>Debug</c> static constructor never tries to reach the real Crestron SDK.
|
||||
/// </summary>
|
||||
public static class DebugServiceRegistration
|
||||
{
|
||||
/// <summary>Gets the registered environment abstraction, or <c>null</c> if not registered.</summary>
|
||||
public static ICrestronEnvironment? Environment { get; private set; }
|
||||
|
||||
/// <summary>Gets the registered console abstraction, or <c>null</c> if not registered.</summary>
|
||||
public static ICrestronConsole? Console { get; private set; }
|
||||
|
||||
/// <summary>Gets the registered data-store abstraction, or <c>null</c> if not registered.</summary>
|
||||
public static ICrestronDataStore? DataStore { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers the service implementations that <c>Debug</c> will use when its
|
||||
/// static constructor runs. Any parameter may be <c>null</c> to leave the
|
||||
/// corresponding service unregistered (the <c>Debug</c> class will skip that
|
||||
/// capability gracefully).
|
||||
/// </summary>
|
||||
public static void Register(
|
||||
ICrestronEnvironment? environment,
|
||||
ICrestronConsole? console,
|
||||
ICrestronDataStore? dataStore)
|
||||
{
|
||||
Environment = environment;
|
||||
Console = console;
|
||||
DataStore = dataStore;
|
||||
}
|
||||
}
|
||||
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Crestron's <c>eDevicePlatform</c> without requiring the Crestron SDK.
|
||||
/// </summary>
|
||||
public enum DevicePlatform
|
||||
{
|
||||
/// <summary>Hardware appliance (e.g. CP4, MC4).</summary>
|
||||
Appliance,
|
||||
/// <summary>Crestron Virtual Control / server runtime.</summary>
|
||||
Server,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Crestron's <c>eRuntimeEnvironment</c>.
|
||||
/// </summary>
|
||||
public enum RuntimeEnvironment
|
||||
{
|
||||
/// <summary>SimplSharpPro program slot (hardware 4-series).</summary>
|
||||
SimplSharpPro,
|
||||
/// <summary>SimplSharp (older 3-series or server environments).</summary>
|
||||
SimplSharp,
|
||||
/// <summary>Any other environment — check for completeness.</summary>
|
||||
Other,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Crestron's <c>ConsoleAccessLevelEnum</c>.
|
||||
/// </summary>
|
||||
public enum ConsoleAccessLevel
|
||||
{
|
||||
AccessAdministrator = 0,
|
||||
AccessOperator = 1,
|
||||
AccessProgrammer = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Crestron's <c>eProgramStatusEventType</c>.
|
||||
/// </summary>
|
||||
public enum ProgramStatusEventType
|
||||
{
|
||||
Starting,
|
||||
Stopping,
|
||||
Paused,
|
||||
Resumed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors the event type used by Crestron's <c>EthernetEventArgs</c>.
|
||||
/// </summary>
|
||||
public enum EthernetEventType
|
||||
{
|
||||
LinkDown = 0,
|
||||
LinkUp = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for Crestron ethernet link events.
|
||||
/// </summary>
|
||||
public class PepperDashEthernetEventArgs : EventArgs
|
||||
{
|
||||
public EthernetEventType EthernetEventType { get; }
|
||||
public short EthernetAdapter { get; }
|
||||
|
||||
public PepperDashEthernetEventArgs(EthernetEventType eventType, short adapter)
|
||||
{
|
||||
EthernetEventType = eventType;
|
||||
EthernetAdapter = adapter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors the set of <c>CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET</c> values
|
||||
/// used across this codebase — does not aim to be exhaustive.
|
||||
/// </summary>
|
||||
public enum EthernetParameterType
|
||||
{
|
||||
GetCurrentIpAddress,
|
||||
GetHostname,
|
||||
GetDomainName,
|
||||
GetLinkStatus,
|
||||
GetCurrentDhcpState,
|
||||
GetCurrentIpMask,
|
||||
GetCurrentRouter,
|
||||
GetMacAddress,
|
||||
GetDnsServer,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Crestron's <c>SocketStatus</c> without requiring the Crestron SDK.
|
||||
/// </summary>
|
||||
public enum PepperDashSocketStatus
|
||||
{
|
||||
SocketNotConnected = 0,
|
||||
SocketConnected = 2,
|
||||
SocketConnectionInProgress = 6,
|
||||
SocketConnectFailed = 11,
|
||||
SocketDisconnecting = 12,
|
||||
SocketBrokenRemotely = 7,
|
||||
}
|
||||
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstracts <c>Crestron.SimplSharp.CrestronConsole</c> to allow unit testing
|
||||
/// without the Crestron SDK.
|
||||
/// </summary>
|
||||
public interface ICrestronConsole
|
||||
{
|
||||
/// <summary>Prints a line to the Crestron console/telnet output.</summary>
|
||||
void PrintLine(string message);
|
||||
|
||||
/// <summary>Prints text (without newline) to the Crestron console/telnet output.</summary>
|
||||
void Print(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a response string to the console for the currently-executing console command.
|
||||
/// </summary>
|
||||
void ConsoleCommandResponse(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new command with the Crestron console.
|
||||
/// </summary>
|
||||
/// <param name="callback">Handler invoked when the command is typed.</param>
|
||||
/// <param name="command">Command name (no spaces).</param>
|
||||
/// <param name="helpText">Help text shown by the Crestron console.</param>
|
||||
/// <param name="accessLevel">Minimum access level required to run the command.</param>
|
||||
void AddNewConsoleCommand(
|
||||
Action<string> callback,
|
||||
string command,
|
||||
string helpText,
|
||||
ConsoleAccessLevel accessLevel);
|
||||
}
|
||||
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstracts <c>Crestron.SimplSharp.CrestronDataStore.CrestronDataStoreStatic</c>
|
||||
/// to allow unit testing without the Crestron SDK.
|
||||
/// </summary>
|
||||
public interface ICrestronDataStore
|
||||
{
|
||||
/// <summary>Initialises the data store. Must be called once before any other operation.</summary>
|
||||
void InitStore();
|
||||
|
||||
/// <summary>Reads an integer value from the local (program-slot) store.</summary>
|
||||
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||
bool TryGetLocalInt(string key, out int value);
|
||||
|
||||
/// <summary>Writes an integer value to the local (program-slot) store.</summary>
|
||||
/// <returns><c>true</c> on success.</returns>
|
||||
bool SetLocalInt(string key, int value);
|
||||
|
||||
/// <summary>Writes an unsigned integer value to the local (program-slot) store.</summary>
|
||||
/// <returns><c>true</c> on success.</returns>
|
||||
bool SetLocalUint(string key, uint value);
|
||||
|
||||
/// <summary>Reads a boolean value from the local (program-slot) store.</summary>
|
||||
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||
bool TryGetLocalBool(string key, out bool value);
|
||||
|
||||
/// <summary>Writes a boolean value to the local (program-slot) store.</summary>
|
||||
/// <returns><c>true</c> on success.</returns>
|
||||
bool SetLocalBool(string key, bool value);
|
||||
}
|
||||
52
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
52
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstracts <c>Crestron.SimplSharp.CrestronEnvironment</c> to allow unit testing
|
||||
/// without the Crestron SDK.
|
||||
/// </summary>
|
||||
public interface ICrestronEnvironment
|
||||
{
|
||||
/// <summary>Gets the platform the program is executing on.</summary>
|
||||
DevicePlatform DevicePlatform { get; }
|
||||
|
||||
/// <summary>Gets the current runtime environment.</summary>
|
||||
RuntimeEnvironment RuntimeEnvironment { get; }
|
||||
|
||||
/// <summary>Gets the platform-appropriate newline string.</summary>
|
||||
string NewLine { get; }
|
||||
|
||||
/// <summary>Gets the application number (program slot).</summary>
|
||||
uint ApplicationNumber { get; }
|
||||
|
||||
/// <summary>Gets the room ID (used in Crestron Virtual Control / server environments).</summary>
|
||||
uint RoomId { get; }
|
||||
|
||||
/// <summary>Raised when program status changes (starting, stopping, etc.).</summary>
|
||||
event EventHandler<ProgramStatusEventArgs> ProgramStatusChanged;
|
||||
|
||||
/// <summary>Raised when the ethernet link changes state.</summary>
|
||||
event EventHandler<PepperDashEthernetEventArgs> EthernetEventReceived;
|
||||
|
||||
/// <summary>Gets the application root directory path.</summary>
|
||||
string GetApplicationRootDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when running on real Crestron hardware.
|
||||
/// Returns <c>false</c> in test / dev environments so that SDK-specific
|
||||
/// sinks and enrichers can be safely skipped.
|
||||
/// </summary>
|
||||
bool IsHardwareRuntime { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for <see cref="ICrestronEnvironment.ProgramStatusChanged"/>.
|
||||
/// </summary>
|
||||
public class ProgramStatusEventArgs : EventArgs
|
||||
{
|
||||
public ProgramStatusEventType EventType { get; }
|
||||
|
||||
public ProgramStatusEventArgs(ProgramStatusEventType eventType)
|
||||
{
|
||||
EventType = eventType;
|
||||
}
|
||||
}
|
||||
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace PepperDash.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstracts <c>Crestron.SimplSharp.CrestronEthernetHelper</c> to allow unit testing
|
||||
/// without the Crestron SDK.
|
||||
/// </summary>
|
||||
public interface IEthernetHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a network parameter string for the specified adapter.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameter to retrieve.</param>
|
||||
/// <param name="ethernetAdapterId">Ethernet adapter index (0 = LAN A).</param>
|
||||
/// <returns>String value of the requested parameter.</returns>
|
||||
string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>PepperDash.Core.Abstractions</RootNamespace>
|
||||
<AssemblyName>PepperDash.Core.Abstractions</AssemblyName>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Deterministic>true</Deterministic>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<Title>PepperDash Core Abstractions</Title>
|
||||
<Company>PepperDash Technologies</Company>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/PepperDash/PepperDashCore</RepositoryUrl>
|
||||
<NullableContextOptions>enable</NullableContextOptions>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<!-- No Crestron SDK reference — this project must remain hardware-agnostic so test projects can reference it -->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
323
src/PepperDash.Core.Tests/Devices/DeviceTests.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using FluentAssertions;
|
||||
using PepperDash.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Core.Tests.Devices;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="Device"/> — the base class for all PepperDash devices.
|
||||
/// These run without Crestron hardware; Debug is initialized with fakes via TestInitializer.
|
||||
/// </summary>
|
||||
public class DeviceTests
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Construction
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SingleArg_SetsKey()
|
||||
{
|
||||
var device = new Device("my-device");
|
||||
|
||||
device.Key.Should().Be("my-device");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SingleArg_SetsNameToEmpty()
|
||||
{
|
||||
var device = new Device("my-device");
|
||||
|
||||
device.Name.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_TwoArg_SetsKeyAndName()
|
||||
{
|
||||
var device = new Device("my-device", "My Device");
|
||||
|
||||
device.Key.Should().Be("my-device");
|
||||
device.Name.Should().Be("My Device");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_KeyWithDot_StillSetsKey()
|
||||
{
|
||||
// The dot triggers a debug log warning but must not prevent construction.
|
||||
var device = new Device("parent.child");
|
||||
|
||||
device.Key.Should().Be("parent.child");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ToString
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ToString_WithName_FormatsKeyDashName()
|
||||
{
|
||||
var device = new Device("cam-01", "Front Camera");
|
||||
|
||||
device.ToString().Should().Be("cam-01 - Front Camera");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_WithoutName_UsesDashPlaceholder()
|
||||
{
|
||||
var device = new Device("cam-01");
|
||||
|
||||
device.ToString().Should().Be("cam-01 - ---");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DefaultDevice
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DefaultDevice_IsNotNull()
|
||||
{
|
||||
Device.DefaultDevice.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultDevice_HasKeyDefault()
|
||||
{
|
||||
Device.DefaultDevice.Key.Should().Be("Default");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CustomActivate / Activate / Deactivate / Initialize
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CustomActivate_DefaultReturnTrue()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
|
||||
device.CallCustomActivate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deactivate_DefaultReturnsTrue()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
|
||||
device.Deactivate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Activate_CallsCustomActivate_AndReturnsItsResult()
|
||||
{
|
||||
var stub = new ActivateTrackingDevice("d1", result: false);
|
||||
|
||||
stub.Activate().Should().BeFalse();
|
||||
stub.CustomActivateCalled.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Activate_TrueWhenCustomActivateReturnsTrue()
|
||||
{
|
||||
var stub = new ActivateTrackingDevice("d1", result: true);
|
||||
|
||||
stub.Activate().Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.CallInitialize();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// PreActivate
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_NoActions_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.PreActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_RunsRegisteredActionsInOrder()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var order = new List<int>();
|
||||
|
||||
device.AddPreActivationAction(() => order.Add(1));
|
||||
device.AddPreActivationAction(() => order.Add(2));
|
||||
device.AddPreActivationAction(() => order.Add(3));
|
||||
|
||||
device.PreActivate();
|
||||
|
||||
order.Should().Equal(1, 2, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreActivate_ContinuesAfterFaultingAction()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var reached = false;
|
||||
|
||||
device.AddPreActivationAction(() => throw new InvalidOperationException("boom"));
|
||||
device.AddPreActivationAction(() => reached = true);
|
||||
|
||||
var act = () => device.PreActivate();
|
||||
|
||||
act.Should().NotThrow("exceptions in individual actions must be caught internally");
|
||||
reached.Should().BeTrue("actions after a faulting action must still run");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// PostActivate
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_NoActions_DoesNotThrow()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var act = () => device.PostActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_RunsRegisteredActionsInOrder()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var order = new List<int>();
|
||||
|
||||
device.AddPostActivationAction(() => order.Add(1));
|
||||
device.AddPostActivationAction(() => order.Add(2));
|
||||
|
||||
device.PostActivate();
|
||||
|
||||
order.Should().Equal(1, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivate_ContinuesAfterFaultingAction()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var reached = false;
|
||||
|
||||
device.AddPostActivationAction(() => throw new Exception("boom"));
|
||||
device.AddPostActivationAction(() => reached = true);
|
||||
|
||||
var act = () => device.PostActivate();
|
||||
|
||||
act.Should().NotThrow();
|
||||
reached.Should().BeTrue();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Pre and Post actions are independent lists
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void PreActivationActions_DoNotRunOnPostActivate()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var preRan = false;
|
||||
|
||||
device.AddPreActivationAction(() => preRan = true);
|
||||
device.PostActivate();
|
||||
|
||||
preRan.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostActivationActions_DoNotRunOnPreActivate()
|
||||
{
|
||||
var device = new TestDevice("d1");
|
||||
var postRan = false;
|
||||
|
||||
device.AddPostActivationAction(() => postRan = true);
|
||||
device.PreActivate();
|
||||
|
||||
postRan.Should().BeFalse();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// OnFalse
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_FiresAction_WhenBoolIsFalse()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse(false, () => fired = true);
|
||||
|
||||
fired.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_DoesNotFireAction_WhenBoolIsTrue()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse(true, () => fired = true);
|
||||
|
||||
fired.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnFalse_DoesNotFireAction_ForNonBoolType()
|
||||
{
|
||||
var device = new Device("d1");
|
||||
var fired = false;
|
||||
|
||||
device.OnFalse("not a bool", () => fired = true);
|
||||
device.OnFalse(0, () => fired = true);
|
||||
device.OnFalse(null!, () => fired = true);
|
||||
|
||||
fired.Should().BeFalse();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Exposes protected Device members so test methods can call them directly.
|
||||
/// </summary>
|
||||
private class TestDevice : Device
|
||||
{
|
||||
public TestDevice(string key) : base(key) { }
|
||||
public TestDevice(string key, string name) : base(key, name) { }
|
||||
|
||||
public void AddPreActivationAction(Action act) => base.AddPreActivationAction(act);
|
||||
public void AddPostActivationAction(Action act) => base.AddPostActivationAction(act);
|
||||
public bool CallCustomActivate() => base.CustomActivate();
|
||||
public void CallInitialize() => base.Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records whether CustomActivate was invoked and returns a configured result.
|
||||
/// Used to verify Activate() correctly delegates to CustomActivate().
|
||||
/// </summary>
|
||||
private sealed class ActivateTrackingDevice : Device
|
||||
{
|
||||
private readonly bool _result;
|
||||
public bool CustomActivateCalled { get; private set; }
|
||||
|
||||
public ActivateTrackingDevice(string key, bool result = true) : base(key)
|
||||
{
|
||||
_result = result;
|
||||
}
|
||||
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
CustomActivateCalled = true;
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// No-op ICrestronConsole that captures output for test assertions.
|
||||
/// </summary>
|
||||
public class CapturingCrestronConsole : ICrestronConsole
|
||||
{
|
||||
public List<string> Lines { get; } = new();
|
||||
public List<string> CommandResponses { get; } = new();
|
||||
public List<(string Command, string HelpText)> RegisteredCommands { get; } = new();
|
||||
|
||||
public void PrintLine(string message) => Lines.Add(message);
|
||||
public void Print(string message) => Lines.Add(message);
|
||||
public void ConsoleCommandResponse(string message) => CommandResponses.Add(message);
|
||||
|
||||
public void AddNewConsoleCommand(
|
||||
Action<string> callback,
|
||||
string command,
|
||||
string helpText,
|
||||
ConsoleAccessLevel accessLevel)
|
||||
{
|
||||
RegisteredCommands.Add((command, helpText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimal no-op ICrestronConsole that discards all output. Useful when you only
|
||||
/// care about the system under test and not what it logs.
|
||||
/// </summary>
|
||||
public class NoOpCrestronConsole : ICrestronConsole
|
||||
{
|
||||
public void PrintLine(string message) { }
|
||||
public void Print(string message) { }
|
||||
public void ConsoleCommandResponse(string message) { }
|
||||
public void AddNewConsoleCommand(Action<string> _, string __, string ___, ConsoleAccessLevel ____) { }
|
||||
}
|
||||
49
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
49
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// Configurable ICrestronEnvironment for unit tests.
|
||||
/// Defaults: Appliance / SimplSharpPro / ApplicationNumber=1.
|
||||
/// </summary>
|
||||
public class FakeCrestronEnvironment : ICrestronEnvironment
|
||||
{
|
||||
public DevicePlatform DevicePlatform { get; set; } = DevicePlatform.Appliance;
|
||||
public RuntimeEnvironment RuntimeEnvironment { get; set; } = RuntimeEnvironment.SimplSharpPro;
|
||||
public string NewLine { get; set; } = "\r\n";
|
||||
public uint ApplicationNumber { get; set; } = 1;
|
||||
public uint RoomId { get; set; } = 0;
|
||||
|
||||
public event EventHandler<ProgramStatusEventArgs>? ProgramStatusChanged;
|
||||
public event EventHandler<PepperDashEthernetEventArgs>? EthernetEventReceived;
|
||||
|
||||
public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHardwareRuntime => false;
|
||||
|
||||
/// <summary>Simulates a program status event for tests.</summary>
|
||||
public void RaiseProgramStatus(ProgramStatusEventType type) =>
|
||||
ProgramStatusChanged?.Invoke(this, new ProgramStatusEventArgs(type));
|
||||
|
||||
/// <summary>Simulates an ethernet event for tests.</summary>
|
||||
public void RaiseEthernetEvent(EthernetEventType type, short adapter = 0) =>
|
||||
EthernetEventReceived?.Invoke(this, new PepperDashEthernetEventArgs(type, adapter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// No-op IEthernetHelper that returns configurable values.
|
||||
/// </summary>
|
||||
public class FakeEthernetHelper : IEthernetHelper
|
||||
{
|
||||
private readonly Dictionary<EthernetParameterType, string> _values = new();
|
||||
|
||||
public FakeEthernetHelper Seed(EthernetParameterType param, string value)
|
||||
{
|
||||
_values[param] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId) =>
|
||||
_values.TryGetValue(parameter, out var v) ? v : string.Empty;
|
||||
}
|
||||
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory ICrestronDataStore backed by a dictionary.
|
||||
/// Use in unit tests to verify that keys are read from and written to the store correctly.
|
||||
/// </summary>
|
||||
public class InMemoryCrestronDataStore : ICrestronDataStore
|
||||
{
|
||||
private readonly Dictionary<string, object> _store = new();
|
||||
|
||||
public bool Initialized { get; private set; }
|
||||
|
||||
public void InitStore() => Initialized = true;
|
||||
|
||||
public bool TryGetLocalInt(string key, out int value)
|
||||
{
|
||||
if (_store.TryGetValue(key, out var raw) && raw is int i)
|
||||
{
|
||||
value = i;
|
||||
return true;
|
||||
}
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetLocalInt(string key, int value)
|
||||
{
|
||||
_store[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetLocalUint(string key, uint value)
|
||||
{
|
||||
_store[key] = (int)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetLocalBool(string key, out bool value)
|
||||
{
|
||||
if (_store.TryGetValue(key, out var raw) && raw is bool b)
|
||||
{
|
||||
value = b;
|
||||
return true;
|
||||
}
|
||||
value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetLocalBool(string key, bool value)
|
||||
{
|
||||
_store[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Seeds a key for testing read paths.</summary>
|
||||
public void Seed(string key, object value) => _store[key] = value;
|
||||
}
|
||||
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using FluentAssertions;
|
||||
using PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Tests.Fakes;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Core.Tests.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for Debug-related service interfaces and implementations.
|
||||
/// These tests verify the behaviour of the abstractions in isolation (no Crestron SDK required).
|
||||
/// </summary>
|
||||
public class DebugServiceTests
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// ICrestronDataStore — InMemoryCrestronDataStore
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void DataStore_InitStore_SetsInitializedFlag()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
store.Initialized.Should().BeFalse("not yet initialized");
|
||||
|
||||
store.InitStore();
|
||||
|
||||
store.Initialized.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_SetAndGetLocalInt_RoundTrips()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
store.SetLocalInt("MyKey", 42).Should().BeTrue();
|
||||
store.TryGetLocalInt("MyKey", out var value).Should().BeTrue();
|
||||
value.Should().Be(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_TryGetLocalInt_ReturnsFalse_WhenKeyAbsent()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
store.TryGetLocalInt("Missing", out var value).Should().BeFalse();
|
||||
value.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_SetAndGetLocalBool_RoundTrips()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
store.SetLocalBool("FlagKey", true).Should().BeTrue();
|
||||
store.TryGetLocalBool("FlagKey", out var value).Should().BeTrue();
|
||||
value.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_TryGetLocalBool_ReturnsFalse_WhenKeyAbsent()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
store.TryGetLocalBool("Missing", out var value).Should().BeFalse();
|
||||
value.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_SetLocalUint_CanBeReadBackAsInt()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
store.SetLocalUint("UintKey", 3u).Should().BeTrue();
|
||||
store.TryGetLocalInt("UintKey", out var value).Should().BeTrue();
|
||||
value.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_Seed_AllowsTestSetupOfReadPaths()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
store.Seed("MyLevel", 2);
|
||||
|
||||
store.TryGetLocalInt("MyLevel", out var level).Should().BeTrue();
|
||||
level.Should().Be(2);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DebugServiceRegistration
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ServiceRegistration_Register_StoresAllThreeServices()
|
||||
{
|
||||
var env = new FakeCrestronEnvironment();
|
||||
var console = new NoOpCrestronConsole();
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
|
||||
DebugServiceRegistration.Register(env, console, store);
|
||||
|
||||
DebugServiceRegistration.Environment.Should().BeSameAs(env);
|
||||
DebugServiceRegistration.Console.Should().BeSameAs(console);
|
||||
DebugServiceRegistration.DataStore.Should().BeSameAs(store);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServiceRegistration_Register_AcceptsNullsWithoutThrowing()
|
||||
{
|
||||
var act = () => DebugServiceRegistration.Register(null, null, null);
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ICrestronEnvironment — FakeCrestronEnvironment
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void FakeEnvironment_DefaultsToAppliance()
|
||||
{
|
||||
var env = new FakeCrestronEnvironment();
|
||||
env.DevicePlatform.Should().Be(DevicePlatform.Appliance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeEnvironment_RaiseProgramStatus_FiresEvent()
|
||||
{
|
||||
var env = new FakeCrestronEnvironment();
|
||||
ProgramStatusEventType? received = null;
|
||||
env.ProgramStatusChanged += (_, e) => received = e.EventType;
|
||||
|
||||
env.RaiseProgramStatus(ProgramStatusEventType.Stopping);
|
||||
|
||||
received.Should().Be(ProgramStatusEventType.Stopping);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeEnvironment_RaiseEthernetEvent_FiresEvent()
|
||||
{
|
||||
var env = new FakeCrestronEnvironment();
|
||||
EthernetEventType? received = null;
|
||||
env.EthernetEventReceived += (_, e) => received = e.EthernetEventType;
|
||||
|
||||
env.RaiseEthernetEvent(EthernetEventType.LinkUp, adapter: 0);
|
||||
|
||||
received.Should().Be(EthernetEventType.LinkUp);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ICrestronConsole — CapturingCrestronConsole
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void CapturingConsole_PrintLine_CapturesMessage()
|
||||
{
|
||||
var console = new CapturingCrestronConsole();
|
||||
|
||||
console.PrintLine("hello world");
|
||||
|
||||
console.Lines.Should().ContainSingle().Which.Should().Be("hello world");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CapturingConsole_AddNewConsoleCommand_RecordsCommandName()
|
||||
{
|
||||
var console = new CapturingCrestronConsole();
|
||||
|
||||
console.AddNewConsoleCommand(_ => { }, "appdebug", "Sets debug level", ConsoleAccessLevel.AccessOperator);
|
||||
|
||||
console.RegisteredCommands.Should().ContainSingle()
|
||||
.Which.Command.Should().Be("appdebug");
|
||||
}
|
||||
}
|
||||
30
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
30
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||
<!-- PepperDash.Core is referenced so we can test Device and other concrete types.
|
||||
DebugServiceRegistration.Register() is called via a [ModuleInitializer] before any
|
||||
test runs, so Debug's static constructor uses fakes and never touches the Crestron SDK. -->
|
||||
<ProjectReference Include="..\PepperDash.Core\PepperDash.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
29
src/PepperDash.Core.Tests/TestInitializer.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Tests.Fakes;
|
||||
|
||||
namespace PepperDash.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Runs once before any type in this assembly is accessed.
|
||||
/// Registers fake Crestron service implementations with <see cref="DebugServiceRegistration"/>
|
||||
/// so that the <c>Debug</c> static constructor uses them instead of the real Crestron SDK.
|
||||
/// This must remain a module initializer (not a test fixture) because the static constructor
|
||||
/// fires the first time <em>any</em> type in PepperDash.Core is referenced — before xUnit
|
||||
/// has a chance to run fixture setup code.
|
||||
/// </summary>
|
||||
internal static class TestInitializer
|
||||
{
|
||||
[ModuleInitializer]
|
||||
internal static void Initialize()
|
||||
{
|
||||
DebugServiceRegistration.Register(
|
||||
new FakeCrestronEnvironment
|
||||
{
|
||||
DevicePlatform = DevicePlatform.Server, // avoids any appliance-only code paths
|
||||
RuntimeEnvironment = RuntimeEnvironment.Other, // skips console command registration
|
||||
},
|
||||
new NoOpCrestronConsole(),
|
||||
new InMemoryCrestronDataStore());
|
||||
}
|
||||
}
|
||||
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using PdCore = PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK.
|
||||
/// </summary>
|
||||
public sealed class CrestronConsoleAdapter : PdCore.ICrestronConsole
|
||||
{
|
||||
public void PrintLine(string message) => CrestronConsole.PrintLine(message);
|
||||
|
||||
public void Print(string message) => CrestronConsole.Print(message);
|
||||
|
||||
public void ConsoleCommandResponse(string message) =>
|
||||
CrestronConsole.ConsoleCommandResponse(message);
|
||||
|
||||
public void AddNewConsoleCommand(
|
||||
Action<string> callback,
|
||||
string command,
|
||||
string helpText,
|
||||
PdCore.ConsoleAccessLevel accessLevel)
|
||||
{
|
||||
var crestronLevel = accessLevel switch
|
||||
{
|
||||
PdCore.ConsoleAccessLevel.AccessAdministrator => ConsoleAccessLevelEnum.AccessAdministrator,
|
||||
PdCore.ConsoleAccessLevel.AccessProgrammer => ConsoleAccessLevelEnum.AccessProgrammer,
|
||||
_ => ConsoleAccessLevelEnum.AccessOperator,
|
||||
};
|
||||
|
||||
// Wrap Action<string> in a lambda — Crestron's delegate is not a standard Action<string>.
|
||||
CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel);
|
||||
}
|
||||
}
|
||||
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Crestron.SimplSharp.CrestronDataStore;
|
||||
using PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK.
|
||||
/// </summary>
|
||||
public sealed class CrestronDataStoreAdapter : ICrestronDataStore
|
||||
{
|
||||
public void InitStore() => CrestronDataStoreStatic.InitCrestronDataStore();
|
||||
|
||||
public bool TryGetLocalInt(string key, out int value)
|
||||
{
|
||||
var err = CrestronDataStoreStatic.GetLocalIntValue(key, out value);
|
||||
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
public bool SetLocalInt(string key, int value)
|
||||
{
|
||||
var err = CrestronDataStoreStatic.SetLocalIntValue(key, value);
|
||||
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
public bool SetLocalUint(string key, uint value)
|
||||
{
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(key, value);
|
||||
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
public bool TryGetLocalBool(string key, out bool value)
|
||||
{
|
||||
var err = CrestronDataStoreStatic.GetLocalBoolValue(key, out value);
|
||||
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
public bool SetLocalBool(string key, bool value)
|
||||
{
|
||||
var err = CrestronDataStoreStatic.SetLocalBoolValue(key, value);
|
||||
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
}
|
||||
71
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
71
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using PdCore = PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK.
|
||||
/// </summary>
|
||||
public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
|
||||
{
|
||||
// Subscribe once in constructor and re-raise as our event types.
|
||||
private event EventHandler<PdCore.ProgramStatusEventArgs>? _programStatusChanged;
|
||||
private event EventHandler<PdCore.PepperDashEthernetEventArgs>? _ethernetEventReceived;
|
||||
|
||||
public CrestronEnvironmentAdapter()
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||
_programStatusChanged?.Invoke(this, new PdCore.ProgramStatusEventArgs(MapProgramStatus(type)));
|
||||
|
||||
CrestronEnvironment.EthernetEventHandler += args =>
|
||||
_ethernetEventReceived?.Invoke(this, new PdCore.PepperDashEthernetEventArgs(
|
||||
args.EthernetEventType == eEthernetEventType.LinkDown
|
||||
? PdCore.EthernetEventType.LinkDown
|
||||
: PdCore.EthernetEventType.LinkUp,
|
||||
(short)args.EthernetAdapter));
|
||||
}
|
||||
|
||||
public PdCore.DevicePlatform DevicePlatform =>
|
||||
CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
||||
? PdCore.DevicePlatform.Appliance
|
||||
: PdCore.DevicePlatform.Server;
|
||||
|
||||
public PdCore.RuntimeEnvironment RuntimeEnvironment =>
|
||||
CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro
|
||||
? PdCore.RuntimeEnvironment.SimplSharpPro
|
||||
: PdCore.RuntimeEnvironment.Other;
|
||||
|
||||
public string NewLine => CrestronEnvironment.NewLine;
|
||||
|
||||
public uint ApplicationNumber => InitialParametersClass.ApplicationNumber;
|
||||
|
||||
public uint RoomId => uint.TryParse(InitialParametersClass.RoomId, out var r) ? r : 0;
|
||||
|
||||
public event EventHandler<PdCore.ProgramStatusEventArgs> ProgramStatusChanged
|
||||
{
|
||||
add => _programStatusChanged += value;
|
||||
remove => _programStatusChanged -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<PdCore.PepperDashEthernetEventArgs> EthernetEventReceived
|
||||
{
|
||||
add => _ethernetEventReceived += value;
|
||||
remove => _ethernetEventReceived -= value;
|
||||
}
|
||||
|
||||
public string GetApplicationRootDirectory() =>
|
||||
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHardwareRuntime => true;
|
||||
|
||||
private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) =>
|
||||
type switch
|
||||
{
|
||||
eProgramStatusEventType.Stopping => PdCore.ProgramStatusEventType.Stopping,
|
||||
eProgramStatusEventType.Paused => PdCore.ProgramStatusEventType.Paused,
|
||||
eProgramStatusEventType.Resumed => PdCore.ProgramStatusEventType.Resumed,
|
||||
_ => PdCore.ProgramStatusEventType.Starting,
|
||||
};
|
||||
}
|
||||
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core.Abstractions;
|
||||
|
||||
namespace PepperDash.Core.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK.
|
||||
/// </summary>
|
||||
public sealed class CrestronEthernetAdapter : IEthernetHelper
|
||||
{
|
||||
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId)
|
||||
{
|
||||
var crestronParam = parameter switch
|
||||
{
|
||||
EthernetParameterType.GetCurrentIpAddress =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS,
|
||||
EthernetParameterType.GetHostname =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME,
|
||||
EthernetParameterType.GetDomainName =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME,
|
||||
EthernetParameterType.GetLinkStatus =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS,
|
||||
EthernetParameterType.GetCurrentDhcpState =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE,
|
||||
EthernetParameterType.GetCurrentIpMask =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK,
|
||||
EthernetParameterType.GetCurrentRouter =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER,
|
||||
EthernetParameterType.GetMacAddress =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS,
|
||||
EthernetParameterType.GetDnsServer =>
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(parameter), parameter, null),
|
||||
};
|
||||
|
||||
return CrestronEthernetHelper.GetEthernetParameter(crestronParam, ethernetAdapterId);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,6 @@ public class Device : IKeyName
|
||||
public Device(string key, string name) : this(key)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
}
|
||||
|
||||
//public Device(DeviceConfig config)
|
||||
@@ -75,7 +74,7 @@ public class Device : IKeyName
|
||||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPreActivationAction(Action act)
|
||||
protected void AddPreActivationAction(Action act)
|
||||
{
|
||||
if (_PreActivationActions == null)
|
||||
_PreActivationActions = new List<Action>();
|
||||
@@ -89,7 +88,7 @@ public class Device : IKeyName
|
||||
/// <summary>
|
||||
/// AddPostActivationAction method
|
||||
/// </summary>
|
||||
public void AddPostActivationAction(Action act)
|
||||
protected void AddPostActivationAction(Action act)
|
||||
{
|
||||
if (_PostActivationActions == null)
|
||||
_PostActivationActions = new List<Action>();
|
||||
@@ -156,7 +155,7 @@ public class Device : IKeyName
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
public virtual bool CustomActivate() { return true; }
|
||||
protected virtual bool CustomActivate() { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Call to deactivate device - unlink events, etc. Overriding classes do not
|
||||
@@ -168,7 +167,7 @@ public class Device : IKeyName
|
||||
/// <summary>
|
||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronLogger;
|
||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
||||
using PdCore = PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
@@ -45,6 +46,13 @@ public static class Debug
|
||||
|
||||
private static ILogger _logger;
|
||||
|
||||
// Injected service abstractions. Populated by DebugServiceRegistration.Register()
|
||||
// before the Debug static constructor runs. Null when running on hardware without
|
||||
// pre-registration (the static ctor falls back to the Crestron SDK directly).
|
||||
private static PdCore.ICrestronEnvironment _environment;
|
||||
private static PdCore.ICrestronConsole _console;
|
||||
private static PdCore.ICrestronDataStore _dataStore;
|
||||
|
||||
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
|
||||
@@ -88,7 +96,7 @@ public static class Debug
|
||||
/// <summary>
|
||||
/// The name of the file containing the current debug settings.
|
||||
/// </summary>
|
||||
public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
|
||||
public static string FileName = "app0Debug.json"; // default; updated in static ctor using _environment.ApplicationNumber
|
||||
|
||||
/// <summary>
|
||||
/// Debug level to set for a given program.
|
||||
@@ -107,7 +115,7 @@ public static class Debug
|
||||
/// <summary>
|
||||
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
|
||||
/// </summary>
|
||||
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
|
||||
public static bool IsRunningOnAppliance; // set in static constructor
|
||||
|
||||
/// <summary>
|
||||
/// Version for the currently loaded PepperDashCore dll
|
||||
@@ -145,102 +153,120 @@ public static class Debug
|
||||
{
|
||||
try
|
||||
{
|
||||
CrestronDataStoreStatic.InitCrestronDataStore();
|
||||
// Pick up services pre-registered by the composition root (or test setup).
|
||||
// If null, fall back to direct Crestron SDK calls for production hardware.
|
||||
_environment = PdCore.DebugServiceRegistration.Environment;
|
||||
_console = PdCore.DebugServiceRegistration.Console;
|
||||
_dataStore = PdCore.DebugServiceRegistration.DataStore;
|
||||
|
||||
IsRunningOnAppliance = _environment?.DevicePlatform == PdCore.DevicePlatform.Appliance;
|
||||
|
||||
// Update FileName now that _environment is available (avoids Crestron SDK ref in field initializer).
|
||||
FileName = $"app{_environment?.ApplicationNumber ?? 0}Debug.json";
|
||||
|
||||
_dataStore?.InitStore();
|
||||
|
||||
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
|
||||
consoleDebugTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
SetDebugLevel(LogEventLevel.Information);
|
||||
CrestronConsole.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
|
||||
_console?.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
|
||||
};
|
||||
|
||||
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
|
||||
|
||||
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
|
||||
|
||||
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
|
||||
|
||||
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
|
||||
|
||||
consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
||||
|
||||
websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
||||
|
||||
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||
|
||||
fileLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||
|
||||
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
websocketSink = TryCreateWebsocketSink();
|
||||
|
||||
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";
|
||||
var appRoot = _environment?.GetApplicationRootDirectory()
|
||||
?? System.IO.Path.GetTempPath();
|
||||
var sep = System.IO.Path.DirectorySeparatorChar;
|
||||
var appNum = _environment?.ApplicationNumber ?? 0;
|
||||
var roomId = _environment?.RoomId ?? 0;
|
||||
|
||||
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
|
||||
var logFilePath = IsRunningOnAppliance
|
||||
? $"{appRoot}{sep}user{sep}debug{sep}app{appNum}{sep}global-log.log"
|
||||
: $"{appRoot}{sep}user{sep}debug{sep}room{roomId}{sep}global-log.log";
|
||||
|
||||
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}";
|
||||
_console?.PrintLine($"Saving log files to {logFilePath}");
|
||||
|
||||
// Build the base Serilog pipeline — sinks that require the Crestron SDK are
|
||||
// added conditionally so the logger remains usable in test environments.
|
||||
_defaultLoggerConfiguration = new LoggerConfiguration()
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.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,
|
||||
retainedFileCountLimit: IsRunningOnAppliance ? 30 : 60,
|
||||
levelSwitch: fileLoggingLevelSwitch
|
||||
);
|
||||
|
||||
// Instantiate the root logger
|
||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||
// Websocket sink is null when DebugWebsocketSink failed to construct (e.g. test env).
|
||||
if (websocketSink != null)
|
||||
_defaultLoggerConfiguration.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch);
|
||||
|
||||
_logger = _loggerConfiguration.CreateLogger();
|
||||
// Get the assembly version and print it to console and the log
|
||||
GetVersion();
|
||||
|
||||
string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
|
||||
// Add Crestron-specific enricher and error-log sink only on real hardware.
|
||||
if (_environment?.IsHardwareRuntime == true)
|
||||
{
|
||||
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||
var errorLogTemplate = IsRunningOnAppliance
|
||||
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
|
||||
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
|
||||
|
||||
_defaultLoggerConfiguration
|
||||
.Enrich.With(new CrestronEnricher())
|
||||
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch);
|
||||
}
|
||||
|
||||
CrestronConsole.PrintLine(msg);
|
||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||
_logger = _loggerConfiguration.CreateLogger();
|
||||
|
||||
GetVersion();
|
||||
|
||||
string msg = IsRunningOnAppliance
|
||||
? $"[App {appNum}] Using PepperDash_Core v{PepperDashCoreVersion}"
|
||||
: $"[Room {roomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||
|
||||
_console?.PrintLine(msg);
|
||||
LogMessage(LogEventLevel.Information, msg);
|
||||
|
||||
IncludedExcludedKeys = new Dictionary<string, object>();
|
||||
|
||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||
if (_environment?.RuntimeEnvironment == PdCore.RuntimeEnvironment.SimplSharpPro)
|
||||
{
|
||||
// Add command to console
|
||||
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
|
||||
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
|
||||
_console?.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
|
||||
"donotloadonnextboot:P [true/false]: Should the application load on next boot",
|
||||
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||
_console?.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||
"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.",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
|
||||
"appdebugclear:P Clears the current custom log",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
|
||||
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
|
||||
}
|
||||
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||
|
||||
// CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||
_console?.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
|
||||
"appdebuglog:P [all] Use \"all\" for full log.",
|
||||
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||
|
||||
_console?.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
|
||||
"appdebugclear:P Clears the current custom log",
|
||||
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||
|
||||
_console?.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
|
||||
"appdebugfilter [params]",
|
||||
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||
}
|
||||
|
||||
DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
|
||||
|
||||
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));
|
||||
_console?.PrintLine($"Program {appNum} will not load config after next boot. Use console command go:{appNum} to load the config manually");
|
||||
|
||||
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
||||
{
|
||||
@@ -250,18 +276,46 @@ public static class Debug
|
||||
catch (Exception ex)
|
||||
{
|
||||
// _logger may not have been initialized yet — do not call LogError here.
|
||||
CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}");
|
||||
// _console may also be null; fall back to CrestronConsole as last resort.
|
||||
// IMPORTANT: this catch block must not throw — any exception escaping a static
|
||||
// constructor permanently faults the type, making the entire class unusable.
|
||||
try { _console?.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||
catch
|
||||
{
|
||||
try { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||
catch { /* CrestronConsole unavailable (test/dev env) — swallow to keep type initializer healthy */ }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Guarantee _logger is never null — all Debug.Log* calls are safe even if the
|
||||
// ctor failed partway through (e.g. on a dev machine without Crestron hardware).
|
||||
_logger ??= new LoggerConfiguration().MinimumLevel.Fatal().CreateLogger();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates the WebSocket sink, returning null if construction fails in a test/dev environment.</summary>
|
||||
private static DebugWebsocketSink? TryCreateWebsocketSink()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_console?.PrintLine("DebugWebsocketSink could not be created in this environment; websocket logging disabled.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetDoNotLoadOnNextBoot()
|
||||
{
|
||||
var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad);
|
||||
if (_dataStore == null) return false;
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
if (!_dataStore.TryGetLocalBool(DoNotLoadOnNextBootKey, out var doNotLoad))
|
||||
{
|
||||
LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err);
|
||||
doNotLoad = false;
|
||||
LogError("Error retrieving DoNotLoadOnNextBoot value");
|
||||
return false;
|
||||
}
|
||||
|
||||
return doNotLoad;
|
||||
@@ -294,17 +348,17 @@ public static class Debug
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
|
||||
if (_dataStore == null) return LogEventLevel.Information;
|
||||
|
||||
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
if (!_dataStore.TryGetLocalInt(levelStoreKey, out int logLevel))
|
||||
{
|
||||
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
|
||||
_console?.Print($"Unable to retrieve stored log level for {levelStoreKey}. Setting 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}");
|
||||
_console?.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
|
||||
@@ -312,7 +366,7 @@ public static class Debug
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
|
||||
_console?.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
|
||||
return LogEventLevel.Information;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +423,7 @@ public static class Debug
|
||||
{
|
||||
if (levelString.Trim() == "?")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
_console?.ConsoleCommandResponse(
|
||||
|
||||
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
|
||||
"[LogLevel] [TimeoutInMinutes]\r\n" +
|
||||
@@ -386,7 +440,7 @@ public static class Debug
|
||||
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
|
||||
_console?.ConsoleCommandResponse($"AppDebug level = {consoleLoggingLevelSwitch.MinimumLevel}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -406,7 +460,7 @@ public static class Debug
|
||||
{
|
||||
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");
|
||||
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
|
||||
return;
|
||||
}
|
||||
SetDebugLevel((uint)levelInt);
|
||||
@@ -420,11 +474,11 @@ public static class Debug
|
||||
return;
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
|
||||
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
|
||||
}
|
||||
catch
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
|
||||
_console?.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +493,7 @@ public static class Debug
|
||||
{
|
||||
logLevel = LogEventLevel.Information;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
||||
_console?.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
||||
|
||||
SetDebugLevel(logLevel, timeout);
|
||||
}
|
||||
@@ -460,17 +514,14 @@ public static class Debug
|
||||
|
||||
consoleLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
|
||||
InitialParametersClass.ApplicationNumber, consoleLoggingLevelSwitch.MinimumLevel);
|
||||
var appNum = _environment?.ApplicationNumber ?? 0;
|
||||
_console?.ConsoleCommandResponse($"[Application {appNum}], Debug level set to {consoleLoggingLevelSwitch.MinimumLevel}\r\n");
|
||||
_console?.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
|
||||
|
||||
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}");
|
||||
if (_dataStore != null && !_dataStore.SetLocalInt(LevelStoreKey, (int)level))
|
||||
_console?.PrintLine($"Error saving console debug level setting");
|
||||
else
|
||||
_console?.ConsoleCommandResponse($"Store result: {(int)level}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -481,10 +532,8 @@ public static class Debug
|
||||
{
|
||||
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: {error}", err);
|
||||
if (_dataStore != null && !_dataStore.SetLocalUint(WebSocketLevelStoreKey, (uint)level))
|
||||
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting");
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
@@ -498,10 +547,8 @@ public static class Debug
|
||||
{
|
||||
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);
|
||||
if (_dataStore != null && !_dataStore.SetLocalUint(ErrorLogLevelStoreKey, (uint)level))
|
||||
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting");
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", errorLogLevelSwitch.MinimumLevel);
|
||||
}
|
||||
@@ -513,10 +560,8 @@ public static class Debug
|
||||
{
|
||||
fileLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(FileLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
|
||||
if (_dataStore != null && !_dataStore.SetLocalUint(FileLevelStoreKey, (uint)level))
|
||||
LogMessage(LogEventLevel.Information, "Error saving File debug level setting");
|
||||
|
||||
LogMessage(LogEventLevel.Information, "File debug level set to {0}", fileLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
@@ -531,7 +576,7 @@ public static class Debug
|
||||
{
|
||||
if (string.IsNullOrEmpty(stateString.Trim()))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
|
||||
_console?.ConsoleCommandResponse($"DoNotLoadOnNextBoot = {DoNotLoadConfigOnNextBoot}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -539,7 +584,7 @@ public static class Debug
|
||||
}
|
||||
catch
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
|
||||
_console?.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,10 +700,8 @@ public static class Debug
|
||||
{
|
||||
DoNotLoadConfigOnNextBoot = state;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogError("Error saving console debug level setting: {err}", err);
|
||||
if (_dataStore != null && !_dataStore.SetLocalBool(DoNotLoadOnNextBootKey, state))
|
||||
LogError("Error saving DoNotLoadConfigOnNextBoot setting");
|
||||
|
||||
LogInformation("Do Not Load Config on Next Boot set to {state}", DoNotLoadConfigOnNextBoot);
|
||||
}
|
||||
@@ -668,9 +711,10 @@ public static class Debug
|
||||
/// </summary>
|
||||
public static void ShowDebugLog(string s)
|
||||
{
|
||||
if (_environment == null) return; // CrestronLogger not available in test environments
|
||||
var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
|
||||
foreach (var l in loglist)
|
||||
CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
|
||||
_console?.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -91,13 +91,18 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
if (!File.Exists(CertPath))
|
||||
CreateCert();
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||
try
|
||||
{
|
||||
if (type == eProgramStatusEventType.Stopping)
|
||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||
{
|
||||
StopServer();
|
||||
}
|
||||
};
|
||||
if (type == eProgramStatusEventType.Stopping)
|
||||
StopServer();
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// CrestronEnvironment is not available in test / dev environments — safe to skip.
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateCert()
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
||||
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
||||
<Compile Remove="Comm\._GenericTcpIpClient.cs" />
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using FluentAssertions;
|
||||
using PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Tests.Fakes;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Tests.Config;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the configuration loading abstractions.
|
||||
/// These verify behaviour of the test fakes and interfaces independently of
|
||||
/// any Crestron SDK types (ConfigReader itself will be tested here once it
|
||||
/// is migrated from Crestron.SimplSharp.CrestronIO to System.IO — see plan Phase 4).
|
||||
/// </summary>
|
||||
public class ConfigServiceFakesTests
|
||||
{
|
||||
[Fact]
|
||||
public void DataStore_MultipleKeys_AreStoredIndependently()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
store.InitStore();
|
||||
|
||||
store.SetLocalInt("KeyA", 1);
|
||||
store.SetLocalInt("KeyB", 2);
|
||||
|
||||
store.TryGetLocalInt("KeyA", out var a);
|
||||
store.TryGetLocalInt("KeyB", out var b);
|
||||
|
||||
a.Should().Be(1);
|
||||
b.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataStore_OverwriteKey_ReturnsNewValue()
|
||||
{
|
||||
var store = new InMemoryCrestronDataStore();
|
||||
store.SetLocalInt("Level", 1);
|
||||
store.SetLocalInt("Level", 5);
|
||||
|
||||
store.TryGetLocalInt("Level", out var level);
|
||||
level.Should().Be(5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeEnvironment_CanBeConfiguredForServer()
|
||||
{
|
||||
var env = new FakeCrestronEnvironment
|
||||
{
|
||||
DevicePlatform = DevicePlatform.Server,
|
||||
ApplicationNumber = 1,
|
||||
RoomId = 42,
|
||||
};
|
||||
|
||||
env.DevicePlatform.Should().Be(DevicePlatform.Server);
|
||||
env.RoomId.Should().Be(42u);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeEthernetHelper_SeedAndRetrieve()
|
||||
{
|
||||
var eth = new FakeEthernetHelper()
|
||||
.Seed(EthernetParameterType.GetCurrentIpAddress, "192.168.1.100")
|
||||
.Seed(EthernetParameterType.GetHostname, "MC4-TEST");
|
||||
|
||||
eth.GetEthernetParameter(EthernetParameterType.GetCurrentIpAddress, 0)
|
||||
.Should().Be("192.168.1.100");
|
||||
|
||||
eth.GetEthernetParameter(EthernetParameterType.GetHostname, 0)
|
||||
.Should().Be("MC4-TEST");
|
||||
|
||||
eth.GetEthernetParameter(EthernetParameterType.GetDomainName, 0)
|
||||
.Should().BeEmpty("unseeded parameter should return empty string");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||
<!-- Reference Core.Tests to share the Fakes folder -->
|
||||
<ProjectReference Include="..\PepperDash.Core.Tests\PepperDash.Core.Tests.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -57,7 +57,7 @@ public class EiscApiAdvanced : BridgeApi, ICommunicationMonitor
|
||||
AddPostActivationAction(RegisterEisc);
|
||||
}
|
||||
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
CommunicationMonitor.Start();
|
||||
return base.CustomActivate();
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace PepperDash.Essentials.Core;
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
Communication.Connect();
|
||||
CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace PepperDash.Essentials.Core;
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Activating");
|
||||
if (!PreventRegistration)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace PepperDash.Essentials.Core
|
||||
/// Make sure that overriding classes call this!
|
||||
/// Registers the Crestron device, connects up to the base events, starts communication monitor
|
||||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Activating");
|
||||
var response = Hardware.RegisterWithLogging(Key);
|
||||
|
||||
@@ -87,7 +87,7 @@ public abstract class EssentialsDevice : Device
|
||||
/// Override this method to perform any initialization that requires all devices to be activated. This method is called automatically after the DeviceManager.AllDevicesActivated event is fired, and should not be called directly.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
CreateMobileControlMessengers();
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace PepperDash.Essentials.Core.Devices;
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
CommunicationMonitor.Start();
|
||||
return true;
|
||||
|
||||
@@ -309,7 +309,7 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
|
||||
GenerateGuidFile(GetGuidFilePath(_config.IpIdInt));
|
||||
@@ -501,12 +501,6 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
// Moved to
|
||||
CurrentRoomSourceNameSig = FusionRoom.CreateOffsetStringSig(JoinMap.Display1CurrentSourceName.JoinNumber, JoinMap.Display1CurrentSourceName.AttributeName,
|
||||
eSigIoMask.InputSigOnly);
|
||||
// Don't think we need to get current status of this as nothing should be alive yet.
|
||||
if (Room is IHasCurrentSourceInfoChange hasCurrentSourceInfoChange)
|
||||
{
|
||||
hasCurrentSourceInfoChange.CurrentSourceChange += Room_CurrentSourceInfoChange;
|
||||
}
|
||||
|
||||
|
||||
FusionRoom.SystemPowerOn.OutputSig.SetSigFalseAction(Room.PowerOnToDefaultOrLastSource);
|
||||
FusionRoom.SystemPowerOff.OutputSig.SetSigFalseAction(() =>
|
||||
@@ -1749,38 +1743,6 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||
return Convert.ToInt32(capture.Groups[1].Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for when room source changes
|
||||
/// </summary>
|
||||
protected void Room_CurrentSourceInfoChange(SourceListItem info, ChangeType type)
|
||||
{
|
||||
// Handle null. Nothing to do when switching from or to null
|
||||
if (info == null || info.SourceDevice == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.SourceDevice is Device dev)
|
||||
{
|
||||
if (type == ChangeType.WillChange)
|
||||
{
|
||||
if (_sourceToFeedbackSigs.ContainsKey(dev))
|
||||
{
|
||||
_sourceToFeedbackSigs[dev].BoolValue = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_sourceToFeedbackSigs.ContainsKey(dev))
|
||||
{
|
||||
_sourceToFeedbackSigs[dev].BoolValue = true;
|
||||
}
|
||||
//var name = (room == null ? "" : room.Name);
|
||||
CurrentRoomSourceNameSig.InputSig.StringValue = dev.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler for Fusion state changes
|
||||
/// </summary>
|
||||
|
||||
@@ -51,16 +51,31 @@ public class MicrophonePrivacyController : EssentialsDevice
|
||||
|
||||
bool _enableLeds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of digital inputs that are used to toggle the privacy state. Each input is expected to be momentary contact closure that triggers a change in the privacy state when activated. The controller will subscribe to the OutputChange event of each input's InputStateFeedback to monitor for changes in the input state and respond accordingly by toggling the privacy state and updating the LED indicators if enabled.
|
||||
/// </summary>
|
||||
public List<IDigitalInput> Inputs { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the GenericRelayDevice that is used to indicate the privacy state with a red LED. When the privacy mode is active, the red LED will be turned on to provide a visual indication of the privacy state. The controller will manage the state of this relay based on changes in the privacy mode, ensuring that it accurately reflects whether the privacy mode is currently active or not.
|
||||
/// </summary>
|
||||
public GenericRelayDevice RedLedRelay { get; private set; }
|
||||
bool _redLedRelayState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the GenericRelayDevice that is used to indicate the privacy state with a green LED. When the privacy mode is inactive, the green LED will be turned on to provide a visual indication of the privacy state. The controller will manage the state of this relay based on changes in the privacy mode, ensuring that it accurately reflects whether the privacy mode is currently active or not.
|
||||
/// </summary>
|
||||
public GenericRelayDevice GreenLedRelay { get; private set; }
|
||||
bool _greenLedRelayState;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPrivacy PrivacyDevice { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the MicrophonePrivacyController class, which initializes the controller with the specified key and configuration. The constructor sets up the necessary properties and collections for managing the digital inputs, LED relays, and privacy device. It also prepares the controller for activation by ensuring that all required components are properly initialized and ready to be used when the controller is activated in the system.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="config"></param>
|
||||
public MicrophonePrivacyController(string key, MicrophonePrivacyControllerConfig config) :
|
||||
base(key)
|
||||
{
|
||||
@@ -69,7 +84,7 @@ public class MicrophonePrivacyController : EssentialsDevice
|
||||
Inputs = new List<IDigitalInput>();
|
||||
}
|
||||
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
foreach (var i in Config.Inputs)
|
||||
{
|
||||
@@ -105,13 +120,15 @@ public class MicrophonePrivacyController : EssentialsDevice
|
||||
|
||||
#region Overrides of Device
|
||||
|
||||
public override void Initialize()
|
||||
/// <inheritdoc />
|
||||
protected override void Initialize()
|
||||
{
|
||||
CheckPrivacyMode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetPrivacyDevice(IPrivacy privacyDevice)
|
||||
{
|
||||
PrivacyDevice = privacyDevice;
|
||||
@@ -240,13 +257,21 @@ public class MicrophonePrivacyController : EssentialsDevice
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating MicrophonePrivacyController devices
|
||||
/// </summary>
|
||||
public class MicrophonePrivacyControllerFactory : EssentialsDeviceFactory<MicrophonePrivacyController>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for the MicrophonePrivacyControllerFactory class, which initializes the factory and sets up the necessary type names for device creation. This factory is responsible for creating instances of the MicrophonePrivacyController device based on the provided configuration and device key when requested by the system.
|
||||
/// </summary>
|
||||
public MicrophonePrivacyControllerFactory()
|
||||
{
|
||||
TypeNames = new List<string>() { "microphoneprivacycontroller" };
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new MIcrophonePrivacyController Device");
|
||||
|
||||
@@ -168,6 +168,20 @@ public class SystemMonitorController : EssentialsBridgeableDevice
|
||||
_uptimePollTimer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls the uptime and last start time from the control system and updates the feedbacks
|
||||
/// This is necessary because there is no event that is fired when these values change, so we have to poll for them
|
||||
/// at a regular interval
|
||||
/// Uptime is also polled on activation to get initial values
|
||||
/// Uptime is polled every 5 minutes (300000 ms) which should be often enough to keep the values updated without causing performance issues
|
||||
/// Note: polling uptime can cause a delay in the feedback update, so it is done in a separate thread to avoid blocking the main thread
|
||||
/// Note: this method uses CrestronConsole.SendControlSystemCommand which can be slow, so it is not recommended to call this method more often than every 5 minutes
|
||||
/// Note: this method will not work on a server as the "uptime" command is not available, but it will not cause any issues either as it will just return an empty string
|
||||
/// Note: if you need more real-time uptime updates, you could consider implementing a custom solution that tracks uptime internally and updates the feedbacks accordingly, but this would require more complex implementation and testing
|
||||
/// Note: if you implement a custom solution for tracking uptime, you should still consider polling the uptime from the control system at a regular interval (e.g. every hour) to ensure that your internal tracking is accurate and to account for any potential issues that may arise with your custom implementation
|
||||
/// Note: if you implement a custom solution for tracking uptime, you should also consider implementing a way to reset the uptime (e.g. on program start) to ensure that it reflects the actual uptime of the control system accurately
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
public void PollUptime(object obj)
|
||||
{
|
||||
var consoleResponse = string.Empty;
|
||||
@@ -306,13 +320,15 @@ public class SystemMonitorController : EssentialsBridgeableDevice
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CustomActivate()
|
||||
/// <inheritdoc />
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
RefreshSystemMonitorData();
|
||||
|
||||
return base.CustomActivate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
|
||||
{
|
||||
var joinMap = new SystemMonitorJoinMap(joinStart);
|
||||
@@ -675,6 +691,9 @@ public class SystemMonitorController : EssentialsBridgeableDevice
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterIndex));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates all the ethernet status feedbacks for this interface
|
||||
/// </summary>
|
||||
public void UpdateEthernetStatus()
|
||||
{
|
||||
HostNameFeedback.FireUpdate();
|
||||
@@ -692,26 +711,77 @@ public class SystemMonitorController : EssentialsBridgeableDevice
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of feedbacks related to the status of a program running on the control system, including its operating state, registration state, and various pieces of information about the program such as its name, compile time, and environment. This class also includes methods for retrieving program information and responding to program change events to keep the feedbacks updated in real-time.
|
||||
/// </summary>
|
||||
public class ProgramStatusFeedbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is fired when any of the program information properties change, allowing external classes to respond to changes in program information in real-time
|
||||
/// </summary>
|
||||
public event EventHandler<ProgramInfoEventArgs> ProgramInfoChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Program associated with this collection of feedbacks
|
||||
/// </summary>
|
||||
public Program Program;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramInfo object that contains detailed information about the program, such as its file name, compile time, environment, and other relevant properties. This object is updated whenever there is a change in the program's operating state or registration state, ensuring that the feedbacks always reflect the most current information about the program.
|
||||
/// </summary>
|
||||
public ProgramInfo ProgramInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramStartedFeedback, which is a BoolFeedback that indicates whether the program is currently started (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is running or not.
|
||||
/// </summary>
|
||||
public BoolFeedback ProgramStartedFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramStoppedFeedback, which is a BoolFeedback that indicates whether the program is currently stopped (true) or not (false). This feedback is updated in real-time based on the program's operating state, allowing external classes to easily monitor whether the program is stopped or not.
|
||||
/// </summary>
|
||||
public BoolFeedback ProgramStoppedFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramRegisteredFeedback, which is a BoolFeedback that indicates whether the program is currently registered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is registered or not.
|
||||
/// </summary>
|
||||
public BoolFeedback ProgramRegisteredFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramUnregisteredFeedback, which is a BoolFeedback that indicates whether the program is currently unregistered (true) or not (false). This feedback is updated in real-time based on the program's registration state, allowing external classes to easily monitor whether the program is unregistered or not.
|
||||
/// </summary>
|
||||
public BoolFeedback ProgramUnregisteredFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramNameFeedback, which is a StringFeedback that provides the name of the program file. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current name of the program file.
|
||||
/// </summary>
|
||||
public StringFeedback ProgramNameFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ProgramCompileTimeFeedback, which is a StringFeedback that provides the compile time of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current compile time of the program.
|
||||
/// </summary>
|
||||
public StringFeedback ProgramCompileTimeFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CrestronDataBaseVersionFeedback, which is a StringFeedback that provides the version of the Crestron database used by the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current version of the Crestron database used by the program.
|
||||
/// </summary>
|
||||
public StringFeedback CrestronDataBaseVersionFeedback;
|
||||
// SIMPL windows version
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EnvironmentVersionFeedback, which is a StringFeedback that provides the environment version of the program. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access the current environment version of the program.
|
||||
/// </summary>
|
||||
public StringFeedback EnvironmentVersionFeedback;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AggregatedProgramInfoFeedback, which is a StringFeedback that provides a JSON serialized string of the entire ProgramInfo object. This feedback is updated in real-time based on changes to the program's information, allowing external classes to easily access all current information about the program in a single feedback.
|
||||
/// </summary>
|
||||
public StringFeedback AggregatedProgramInfoFeedback;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the ProgramStatusFeedbacks class, which initializes all feedbacks based on the provided Program object and retrieves the initial program information to populate the feedbacks with accurate data. This constructor also sets up the necessary event handlers to ensure that the feedbacks are updated in real-time as changes occur to the program's operating state or registration state.
|
||||
/// </summary>
|
||||
/// <param name="program"></param>
|
||||
public ProgramStatusFeedbacks(Program program)
|
||||
{
|
||||
ProgramInfo = new ProgramInfo(program.Number);
|
||||
@@ -851,6 +921,9 @@ public class ProgramStatusFeedbacks
|
||||
OnProgramInfoChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires the ProgramInfoChanged event to notify external classes that they should update any properties related to program information based on changes to the program's operating state or registration state. This method is called whenever there is a change in the program's information, ensuring that all feedbacks and external classes that rely on program information are always up-to-date with the most current information about the program.
|
||||
/// </summary>
|
||||
public void OnProgramInfoChanged()
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Debug, "Firing ProgramInfoChanged for slot: {0}", Program.Number);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class RoomOnToDefaultSourceWhenOccupied : ReconfigurableDevice
|
||||
});
|
||||
}
|
||||
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
SetUpDevice();
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ public abstract class EssentialsRoomBase : ReconfigurableDevice, IEssentialsRoom
|
||||
});
|
||||
}
|
||||
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
SetUpMobileControl();
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class RetriggerableTimer : EssentialsDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
if (_propertiesConfig.StartTimerOnActivation)
|
||||
{
|
||||
|
||||
@@ -208,7 +208,7 @@ public class EssentialsWebApi : EssentialsDevice
|
||||
/// <summary>
|
||||
/// Initializes the CWS class
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
AddRoute(new HttpCwsRoute("apiPaths")
|
||||
{
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace PepperDash.Essentials.Devices.Common.Shades
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
//Create ISwitchedOutput objects based on props
|
||||
switch (Mode)
|
||||
|
||||
@@ -45,7 +45,7 @@ public class RelayControlledShade : ShadeBase, IShadesOpenCloseStop
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
//Create ISwitchedOutput objects based on props
|
||||
OpenRelay = GetSwitchedOutputFromDevice(Config.Relays.Open);
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ShadeController : EssentialsDevice, IShades
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
foreach (var shadeConfig in Config.Shades)
|
||||
{
|
||||
|
||||
@@ -8,20 +8,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
/// </summary>
|
||||
public class DeviceStateMessageBase : DeviceMessageBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The interfaces implmented by the device sending the messsage
|
||||
/// </summary>
|
||||
[JsonProperty("interfaces")]
|
||||
public List<string> Interfaces { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the interfaces implemented by the device sending the message
|
||||
/// </summary>
|
||||
/// <param name="interfaces"></param>
|
||||
public void SetInterfaces(List<string> interfaces)
|
||||
{
|
||||
Interfaces = interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -258,8 +258,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
throw new ArgumentNullException("device");
|
||||
}
|
||||
|
||||
message.SetInterfaces(_deviceInterfaces);
|
||||
|
||||
message.Key = _device.Key;
|
||||
|
||||
message.Name = _device.Name;
|
||||
@@ -285,8 +283,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||
{
|
||||
try
|
||||
{
|
||||
deviceState.SetInterfaces(_deviceInterfaces);
|
||||
|
||||
deviceState.Key = _device.Key;
|
||||
|
||||
deviceState.Name = _device.Name;
|
||||
|
||||
@@ -1283,7 +1283,7 @@ namespace PepperDash.Essentials
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
if (!Config.EnableMessengerSubscriptions)
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace PepperDash.Essentials.Room.MobileControl
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
this.LogDebug("Final activation. Setting up actions and feedbacks");
|
||||
//SetupFunctions();
|
||||
|
||||
@@ -364,7 +364,7 @@ namespace PepperDash.Essentials.Touchpanel
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
public override bool CustomActivate()
|
||||
protected override bool CustomActivate()
|
||||
{
|
||||
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
/// Initialize method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -276,7 +276,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
||||
{
|
||||
ClientCertificateRequired = false,
|
||||
CheckCertificateRevocation = false,
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11
|
||||
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.Diagnostics;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Abstractions;
|
||||
using PepperDash.Core.Adapters;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Bridges;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
@@ -46,6 +48,14 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register Crestron service adapters BEFORE the first reference to Debug,
|
||||
// so that Debug's static constructor uses these implementations instead of
|
||||
// calling the Crestron SDK statics directly.
|
||||
DebugServiceRegistration.Register(
|
||||
new CrestronEnvironmentAdapter(),
|
||||
new CrestronConsoleAdapter(),
|
||||
new CrestronDataStoreAdapter());
|
||||
|
||||
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400;
|
||||
|
||||
Global.ControlSystem = this;
|
||||
@@ -261,6 +271,8 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
|
||||
|
||||
_ = new Core.DeviceFactory();
|
||||
|
||||
LoadAssets();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials load from configuration");
|
||||
|
||||
var filesReady = SetupFilesystem();
|
||||
@@ -622,6 +634,65 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
|
||||
var devToolsZipFiles = applicationDirectory.GetFiles("essentials-devtools*.zip");
|
||||
|
||||
if (devToolsZipFiles.Length > 1)
|
||||
{
|
||||
throw new Exception("Multiple essentials-devtools zip files found in application directory. Please ensure only one essentials-devtools*.zip file is present and retry.");
|
||||
}
|
||||
|
||||
if (devToolsZipFiles.Length == 1)
|
||||
{
|
||||
var devToolsZipFile = devToolsZipFiles[0];
|
||||
var programDir = new DirectoryInfo(Global.FilePathPrefix.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
|
||||
var userOrNvramDir = programDir.Parent;
|
||||
var rootDir = userOrNvramDir?.Parent;
|
||||
if (rootDir == null)
|
||||
{
|
||||
throw new Exception($"Unable to determine root directory for debug html extraction. Current path: {Global.FilePathPrefix}");
|
||||
}
|
||||
var debugDir = Path.Combine(Path.Combine(rootDir.FullName, "html"), "debug");
|
||||
var debugRoot = System.IO.Path.GetFullPath(debugDir);
|
||||
if (!debugRoot.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
|
||||
!debugRoot.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
|
||||
{
|
||||
debugRoot += Path.DirectorySeparatorChar;
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Information, "Found essentials-devtools zip file: {zipFile:l}... Unzipping to {Destination}...", devToolsZipFile.FullName, debugDir);
|
||||
using (var archive = ZipFile.OpenRead(devToolsZipFile.FullName))
|
||||
{
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
var destinationPath = Path.Combine(debugDir, entry.FullName);
|
||||
var fullDest = System.IO.Path.GetFullPath(destinationPath);
|
||||
if (!fullDest.StartsWith(debugRoot, StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException($"Entry '{entry.FullName}' is trying to extract outside of the target directory.");
|
||||
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (File.Exists(destinationPath))
|
||||
File.Delete(destinationPath);
|
||||
|
||||
var parentDir = Path.GetDirectoryName(destinationPath);
|
||||
if (!string.IsNullOrEmpty(parentDir))
|
||||
Directory.CreateDirectory(parentDir);
|
||||
|
||||
entry.ExtractToFile(destinationPath, true);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Extracted: {entry:l} to {Destination}", entry.FullName, destinationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleaning up devtools zip files
|
||||
foreach (var file in devToolsZipFiles)
|
||||
{
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
|
||||
var jsonFiles = applicationDirectory.GetFiles("*configurationFile*.json");
|
||||
|
||||
if (jsonFiles.Length > 1)
|
||||
|
||||
Reference in New Issue
Block a user