Compare commits
119 Commits
v2.3.0-bet
...
net8-updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4dce9e493 | ||
|
|
63f86e7499 | ||
|
|
a088166de9 | ||
|
|
c2ab2f34b7 | ||
|
|
083c270cf3 | ||
|
|
9c81546a07 | ||
|
|
d83266f634 | ||
|
|
bc60106afd | ||
|
|
4ee62088fa | ||
|
|
90b6f258f0 | ||
|
|
e1e32cea6f | ||
|
|
e31df338d6 | ||
|
|
d14058fc32 | ||
|
|
04d6508c80 | ||
|
|
1cbc8194ec | ||
|
|
6d2cd75cbe | ||
|
|
8b873b7248 | ||
|
|
58a2a5c008 | ||
|
|
cc7e2ab675 | ||
|
|
dc900f3f31 | ||
|
|
562f0ba793 | ||
|
|
9c3c924a29 | ||
|
|
9b5af60a46 | ||
|
|
a99b0a1fac | ||
|
|
7591913a9c | ||
|
|
66a6612b65 | ||
|
|
688cf34153 | ||
|
|
0c59237232 | ||
|
|
88eec9a3f1 | ||
|
|
fda4a5a816 | ||
|
|
471d5b701b | ||
|
|
96ac266d24 | ||
|
|
c1809459a6 | ||
|
|
1a9e1087de | ||
|
|
8d55615837 | ||
|
|
19e799f11d | ||
|
|
a3c1c444b7 | ||
|
|
9f70e3c721 | ||
|
|
c9b3205736 | ||
|
|
253b2cddaf | ||
|
|
d96edfa8d0 | ||
|
|
95c1c01396 | ||
|
|
9c94806e4f | ||
|
|
183879f1c4 | ||
|
|
f3159738ce | ||
|
|
2c5cae9f41 | ||
|
|
7178d8e284 | ||
|
|
af98a92f8c | ||
|
|
0a6896910d | ||
|
|
9b1dd099f6 | ||
|
|
3f5269de2f | ||
|
|
60f1adcd35 | ||
|
|
12c8660015 | ||
|
|
ec6aeb17f6 | ||
|
|
e7c3fcbbd9 | ||
|
|
0c7ec82529 | ||
|
|
feb99ecbb6 | ||
|
|
91dc655103 | ||
|
|
bf31fb10eb | ||
|
|
d78b9ea313 | ||
|
|
15172a5509 | ||
|
|
5e21bad596 | ||
|
|
a4de9f2241 | ||
|
|
13cd84b73d | ||
|
|
81a01b7960 | ||
|
|
d9dc70bea2 | ||
|
|
2368f0c8cc | ||
|
|
4a77955987 | ||
|
|
a7e4d1b7c1 | ||
|
|
6295e10c08 | ||
|
|
b9553f486a | ||
|
|
e08f250ee8 | ||
|
|
e38ac6bab5 | ||
|
|
aaee3d1317 | ||
|
|
c40acca47a | ||
|
|
b128199d25 | ||
|
|
cc9492938b | ||
|
|
be58a0bc29 | ||
|
|
e8276c4165 | ||
|
|
817b5eccb5 | ||
|
|
702443f953 | ||
|
|
8406f69e0d | ||
|
|
1306247c32 | ||
|
|
d3d7b400ae | ||
|
|
9db980ead1 | ||
|
|
c9c3a74f2f | ||
|
|
116d83394a | ||
|
|
d99095e8ce | ||
|
|
60e705ea8b | ||
|
|
e86ab8fa8b | ||
|
|
9b8e452eb4 | ||
|
|
d8c7e3cfc7 | ||
|
|
eeb106c489 | ||
|
|
c1d62ea5d4 | ||
|
|
9148cfd819 | ||
|
|
60550caf99 | ||
|
|
c9d86bd5dd | ||
|
|
59baa74dd7 | ||
|
|
bf31bf9e93 | ||
|
|
ee8776cfb1 | ||
|
|
0b59990532 | ||
|
|
8d3fd343f1 | ||
|
|
372274d9fa | ||
|
|
b2b257020f | ||
|
|
403c03491c | ||
|
|
3770c2a47d | ||
|
|
5f4a1f768e | ||
|
|
6f58e18d14 | ||
|
|
7eed7866f1 | ||
|
|
c5403f33c5 | ||
|
|
c9f10ecb90 | ||
|
|
ef2da21c2a | ||
|
|
b0920746d1 | ||
|
|
b531d724ff | ||
|
|
1b17d92ee0 | ||
|
|
60fc0298ec | ||
|
|
2c0739df4b | ||
|
|
4e43565c1a | ||
|
|
97e157b5b6 |
56
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: CI Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop, net8-updates ]
|
||||
pull_request:
|
||||
branches: [ main, develop, net8-updates ]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
|
||||
|
||||
- name: Generate coverage report
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.0
|
||||
with:
|
||||
reports: coverage/**/coverage.cobertura.xml
|
||||
targetdir: coverage-report
|
||||
reporttypes: Html;Cobertura;MarkdownSummary
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage-report/Cobertura.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Write coverage summary
|
||||
run: cat coverage-report/Summary.md >> $GITHUB_STEP_SUMMARY
|
||||
if: always()
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
coverage/
|
||||
coverage-report/
|
||||
247
.github/workflows/essentials-3-dev-build.yml
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
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 "=================================="
|
||||
45
.github/workflows/publish-docs.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Publish Docs
|
||||
|
||||
# Trigger the action on push to main
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
actions: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish-docs:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Dotnet Setup
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.x
|
||||
|
||||
- run: dotnet tool update -g docfx
|
||||
- run: docfx ./docs/docfx.json
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: './docs/_site'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
2
.gitignore
vendored
@@ -392,3 +392,5 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
|
||||
.DS_Store
|
||||
/._PepperDash.Essentials.sln
|
||||
.vscode/settings.json
|
||||
_site/
|
||||
api/
|
||||
0
Crestron-Library-Usage-Analysis.md
Normal file
88
PROGRESS_NET8_MOCKING.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# .NET 8 Upgrade Progress - Crestron Mocking
|
||||
|
||||
## Current Status (August 13, 2025)
|
||||
|
||||
### ✅ Completed Tasks
|
||||
1. **Namespace Migration**: Successfully migrated 26+ files from `Crestron.SimplSharp.CrestronIO` to `System.IO`
|
||||
2. **Main Projects Building**: All main solution projects (PepperDash.Essentials, PepperDash.Essentials.Core, etc.) are building successfully for .NET 8
|
||||
3. **CrestronMock Project Structure**: Established comprehensive mock structure with proper namespace hierarchy
|
||||
4. **Duplicate Definition Resolution**: Resolved 37+ duplicate type definition errors by cleaning up conflicting files
|
||||
5. **HTTP/HTTPS Client Mocks**: Implemented complete HTTP/HTTPS client mocks with proper instance-based architecture
|
||||
6. **Core Networking Mocks**: Basic TCP/UDP client/server mock implementations created
|
||||
|
||||
### 🔄 In Progress - PepperDash.Core Test Configuration
|
||||
Currently working on making PepperDash.Core build successfully with Test configuration using CrestronMock implementations.
|
||||
|
||||
#### Recent Progress:
|
||||
- ✅ Removed duplicate WebAndNetworking_New.cs file (eliminated 37 duplicate errors)
|
||||
- ✅ Cleaned duplicate type definitions from Extensions.cs
|
||||
- ✅ Implemented comprehensive HTTP/HTTPS client mocks with proper method signatures
|
||||
- ✅ Added missing TCP client properties and methods (LocalPortNumberOfClient, callback overloads)
|
||||
- 🔄 **Currently fixing**: TCPServer missing _bufferSize field and additional constructor overloads
|
||||
|
||||
#### Last Action Taken:
|
||||
Working on TCPServer.cs - added 2-parameter constructor but need to add missing `_bufferSize` private field.
|
||||
|
||||
### 🎯 Immediate Next Steps
|
||||
1. **Fix TCPServer.cs**:
|
||||
- Add missing `private int _bufferSize;` field
|
||||
- Add missing event handler properties (SocketStatusChange)
|
||||
- Add missing method overloads for SendDataAsync/ReceiveDataAsync
|
||||
|
||||
2. **Complete Remaining Mock Types**:
|
||||
- UDPServer properties (IPAddressLastMessageReceivedFrom, IPPortLastMessageReceivedFrom, IncomingDataBuffer)
|
||||
- SecureTCPServer/SecureTCPClient missing methods
|
||||
- CrestronQueue.TryToEnqueue method
|
||||
- ProgramStatusEventHandler delegate
|
||||
- Console command response methods
|
||||
|
||||
3. **System Types & Environment**:
|
||||
- InitialParametersClass properties (ApplicationNumber, RoomId, RoomName, etc.)
|
||||
- CrestronEnvironment methods (Sleep, OSVersion, GetTimeZone, etc.)
|
||||
- CrestronDataStoreStatic methods (InitCrestronDataStore, SetLocalIntValue, etc.)
|
||||
- IPAddress type and related networking types
|
||||
|
||||
### 📊 Build Status
|
||||
- **Main Projects**: ✅ All building successfully for .NET 8
|
||||
- **PepperDash.Core Test Config**: ❌ Multiple compilation errors (see below)
|
||||
- **Error Count**: ~150+ compilation errors remaining (down from 200+)
|
||||
|
||||
### 🚨 Key Error Categories Remaining
|
||||
1. **Missing Properties/Methods**: TCPClient.LocalPortNumberOfClient, UDPServer properties, etc.
|
||||
2. **Missing Types**: ProgramStatusEventHandler, SocketException, IPAddress
|
||||
3. **Method Signature Mismatches**: SendDataAsync/ReceiveDataAsync parameter counts
|
||||
4. **Enum Values**: eProgramStatusEventType.Stopping, ETHERNET_PARAMETER_TO_GET constants
|
||||
5. **Constructor Overloads**: TCPServer 2-parameter constructor, CrestronQueue constructor
|
||||
|
||||
### 📁 File Status
|
||||
#### ✅ Complete/Stable:
|
||||
- `WebAndNetworking.cs` - HTTP/HTTPS clients with proper namespace separation
|
||||
- `Extensions.cs` - CrestronInvoke and CrestronEthernetHelper (cleaned of duplicates)
|
||||
- `Console.cs` - ErrorLog, CrestronDataStoreStatic basics
|
||||
- `CrestronLogger.cs` - Proper namespace structure
|
||||
|
||||
#### 🔄 In Progress:
|
||||
- `TCPClient.cs` - Added most properties/methods, needs final validation
|
||||
- `TCPServer.cs` - Missing _bufferSize field, needs event handlers
|
||||
- `UDPServer.cs` - Missing properties and method overloads
|
||||
- `SystemTypes.cs` - Needs InitialParametersClass and CrestronEnvironment extensions
|
||||
|
||||
### 🧪 Test Strategy
|
||||
- **Transparent Mocking**: No modifications to PepperDash.Core source files required
|
||||
- **Test Configuration**: Uses CrestronMock project references instead of real Crestron libraries
|
||||
- **API Compatibility**: Mock implementations maintain identical public API surface
|
||||
|
||||
### 🔄 Command to Continue
|
||||
```bash
|
||||
cd /Users/awelker/source/pepperdash/Essentials
|
||||
dotnet build src/PepperDash.Core/PepperDash.Core.csproj -c Test --verbosity minimal
|
||||
```
|
||||
|
||||
### 📝 Notes
|
||||
- User has been manually editing files, so always check current file contents before making changes
|
||||
- Focus on Test configuration only - don't modify Debug/Release builds
|
||||
- All namespace migration work is complete and should be preserved
|
||||
- HTTP/HTTPS mocking architecture is solid and working well
|
||||
|
||||
### 🎯 Success Criteria
|
||||
Goal: Clean build of PepperDash.Core with Test configuration, enabling .NET 8 unit testing with transparent Crestron API mocking.
|
||||
@@ -1,97 +1,234 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33213.308
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Devices.Common", "src\PepperDash.Essentials.Devices.Common\PepperDash.Essentials.Devices.Common.csproj", "{53E204B7-97DD-441D-A96C-721DF014DF82}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials", "src\PepperDash.Essentials\PepperDash.Essentials.csproj", "{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core", "src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj", "{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile Control", "Mobile Control", "{B24989D7-32B5-48D5-9AE1-5F3B17D25206}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl", "src\PepperDash.Essentials.MobileControl\PepperDash.Essentials.MobileControl.csproj", "{F6D362DE-2256-44B1-927A-8CE4705D839A}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl.Messengers", "src\PepperDash.Essentials.MobileControl.Messengers\PepperDash.Essentials.MobileControl.Messengers.csproj", "{B438694F-8FF7-464A-9EC8-10427374471F}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Essentials", "Essentials", "{AD98B742-8D85-481C-A69D-D8D8ABED39EA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.ActiveCfg = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug 4.7.2|Any CPU.Build.0 = Debug 4.7.2|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33213.308
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Devices.Common", "src\PepperDash.Essentials.Devices.Common\PepperDash.Essentials.Devices.Common.csproj", "{53E204B7-97DD-441D-A96C-721DF014DF82}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials", "src\PepperDash.Essentials\PepperDash.Essentials.csproj", "{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core", "src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj", "{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile Control", "Mobile Control", "{B24989D7-32B5-48D5-9AE1-5F3B17D25206}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl", "src\PepperDash.Essentials.MobileControl\PepperDash.Essentials.MobileControl.csproj", "{F6D362DE-2256-44B1-927A-8CE4705D839A}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.MobileControl.Messengers", "src\PepperDash.Essentials.MobileControl.Messengers\PepperDash.Essentials.MobileControl.Messengers.csproj", "{B438694F-8FF7-464A-9EC8-10427374471F}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {E5336563-1194-501E-BC4A-79AD9283EF90}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Essentials", "Essentials", "{AD98B742-8D85-481C-A69D-D8D8ABED39EA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrestronMock", "src\CrestronMock\CrestronMock.csproj", "{01191C7B-606D-4169-81B0-BC8BC1623CE9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EssentialsTests", "Tests\EssentialsTests\EssentialsTests.csproj", "{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Essentials.Core.Tests", "src\tests\PepperDash.Essentials.Core.Tests\PepperDash.Essentials.Core.Tests.csproj", "{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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
|
||||
Test|Any CPU = Test|Any CPU
|
||||
Test|x64 = Test|x64
|
||||
Test|x86 = Test|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|x64.Build.0 = Test|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82}.Test|x86.Build.0 = Test|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
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|x64.Build.0 = Test|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E}.Test|x86.Build.0 = Test|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
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|x64.Build.0 = Test|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B}.Test|x86.Build.0 = Test|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
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|x64.Build.0 = Test|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A}.Test|x86.Build.0 = Test|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
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|x64.Build.0 = Test|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F}.Test|x86.Build.0 = Test|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
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|x64.Build.0 = Test|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Test|x86.Build.0 = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|x64.Build.0 = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9}.Test|x86.Build.0 = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|x64.ActiveCfg = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|x64.Build.0 = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|x86.ActiveCfg = Test|Any CPU
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB}.Test|x86.Build.0 = Test|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|x64.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|x64.Build.0 = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|x86.ActiveCfg = Debug|Any CPU
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6}.Test|x86.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{53E204B7-97DD-441D-A96C-721DF014DF82} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{CB3B11BA-625C-4D35-B663-FDC5BE9A230E} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{3D192FED-8FFC-4CB5-B5F7-BA307ABA254B} = {AD98B742-8D85-481C-A69D-D8D8ABED39EA}
|
||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{01191C7B-606D-4169-81B0-BC8BC1623CE9} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
{3EEC6E2D-ED96-4929-8BBB-C73BE499A4EB} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
{E86229FE-9400-4F7E-B4CB-C43637FEE6A6} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
166
TESTING_STRATEGY.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# PepperDash Essentials Unit Testing Strategy
|
||||
|
||||
## Problem Statement
|
||||
The PepperDash Essentials framework is tightly coupled to Crestron hardware libraries that only run on Crestron devices, making it impossible to run unit tests on development machines or in CI/CD pipelines.
|
||||
|
||||
## Solution: Abstraction Layer Pattern
|
||||
|
||||
### 1. Core Abstractions Created
|
||||
We've implemented abstraction interfaces that decouple business logic from Crestron hardware:
|
||||
|
||||
- **`ICrestronControlSystem`** - Abstracts the control system hardware
|
||||
- **`IRelayPort`** - Abstracts relay functionality
|
||||
- **`IDigitalInput`** - Abstracts digital inputs with event handling
|
||||
- **`IVersiPort`** - Abstracts VersiPort I/O
|
||||
|
||||
### 2. Adapter Pattern Implementation
|
||||
Created adapter classes that wrap Crestron objects in production:
|
||||
|
||||
```csharp
|
||||
// Production code uses adapters
|
||||
var controlSystem = new CrestronControlSystemAdapter(Global.ControlSystem);
|
||||
var processor = new CrestronProcessorTestable("key", controlSystem);
|
||||
|
||||
// Test code uses mocks
|
||||
var mockControlSystem = new Mock<ICrestronControlSystem>();
|
||||
var processor = new CrestronProcessorTestable("key", mockControlSystem.Object);
|
||||
```
|
||||
|
||||
### 3. Testable Classes
|
||||
Refactored classes to accept abstractions via dependency injection:
|
||||
|
||||
- **`CrestronProcessorTestable`** - Accepts `ICrestronControlSystem`
|
||||
- **`GenericRelayDeviceTestable`** - Accepts `IRelayPort`
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Identify Dependencies
|
||||
```bash
|
||||
# Find Crestron dependencies
|
||||
grep -r "using Crestron" --include="*.cs"
|
||||
```
|
||||
|
||||
### Step 2: Create Abstractions
|
||||
Define interfaces that mirror the Crestron API surface you need:
|
||||
```csharp
|
||||
public interface IRelayPort
|
||||
{
|
||||
void Open();
|
||||
void Close();
|
||||
void Pulse(int delayMs);
|
||||
bool State { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Implement Adapters
|
||||
Wrap Crestron objects with adapters:
|
||||
```csharp
|
||||
public class RelayPortAdapter : IRelayPort
|
||||
{
|
||||
private readonly Relay _relay;
|
||||
public void Open() => _relay.Open();
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Refactor Classes
|
||||
Accept abstractions in constructors:
|
||||
```csharp
|
||||
public class CrestronProcessorTestable
|
||||
{
|
||||
public CrestronProcessorTestable(string key, ICrestronControlSystem processor)
|
||||
{
|
||||
// Use abstraction instead of concrete type
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Write Tests
|
||||
Use mocking frameworks to test business logic:
|
||||
```csharp
|
||||
[Fact]
|
||||
public void OpenRelay_CallsRelayPortOpen()
|
||||
{
|
||||
var mockRelay = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test", mockRelay.Object);
|
||||
|
||||
device.OpenRelay();
|
||||
|
||||
mockRelay.Verify(r => r.Open(), Times.Once);
|
||||
}
|
||||
```
|
||||
|
||||
## Test Project Structure
|
||||
```
|
||||
tests/
|
||||
├── PepperDash.Essentials.Core.Tests/
|
||||
│ ├── Abstractions/ # Tests for abstraction adapters
|
||||
│ ├── Devices/ # Device-specific tests
|
||||
│ └── *.csproj # Test project file
|
||||
└── README.md # Testing documentation
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
The `.github/workflows/ci.yml` file runs tests automatically on:
|
||||
- Push to main/develop branches
|
||||
- Pull requests
|
||||
- Generates code coverage reports
|
||||
|
||||
### Running Tests Locally
|
||||
```bash
|
||||
# Run all tests
|
||||
dotnet test
|
||||
|
||||
# Run with coverage
|
||||
dotnet test --collect:"XPlat Code Coverage"
|
||||
|
||||
# Run specific tests
|
||||
dotnet test --filter "FullyQualifiedName~CrestronProcessor"
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Unit Testing Without Hardware** - Tests run on any machine
|
||||
2. **CI/CD Integration** - Automated testing in pipelines
|
||||
3. **Better Design** - Encourages SOLID principles
|
||||
4. **Faster Development** - No need for hardware to test logic
|
||||
5. **Higher Code Quality** - Catch bugs before deployment
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### For Existing Code
|
||||
1. Identify classes with Crestron dependencies
|
||||
2. Create abstraction interfaces
|
||||
3. Implement adapters
|
||||
4. Create testable versions accepting abstractions
|
||||
5. Write unit tests
|
||||
|
||||
### For New Code
|
||||
1. Always code against abstractions, not Crestron types
|
||||
2. Use dependency injection
|
||||
3. Write tests first (TDD approach)
|
||||
|
||||
## Current Test Coverage
|
||||
- ✅ CrestronProcessor relay management
|
||||
- ✅ GenericRelayDevice operations
|
||||
- ✅ Digital input event handling
|
||||
- ✅ VersiPort analog/digital operations
|
||||
|
||||
## Next Steps
|
||||
1. Expand abstractions for more Crestron components
|
||||
2. Increase test coverage across all modules
|
||||
3. Add integration tests with mock hardware
|
||||
4. Document testing best practices
|
||||
5. Create code generation tools for adapters
|
||||
|
||||
## Tools Used
|
||||
- **xUnit** - Test framework
|
||||
- **Moq** - Mocking library
|
||||
- **FluentAssertions** - Readable assertions
|
||||
- **Coverlet** - Code coverage
|
||||
- **GitHub Actions** - CI/CD
|
||||
|
||||
## Summary
|
||||
By introducing an abstraction layer between the business logic and Crestron hardware dependencies, we've successfully enabled unit testing for the PepperDash Essentials framework. This approach allows development and testing without physical hardware while maintaining full compatibility with Crestron systems in production.
|
||||
@@ -1,47 +0,0 @@
|
||||
devjson:1 {"deviceKey":"display-1-comMonitor","methodName":"PrintStatus"}
|
||||
|
||||
devjson:1 {"deviceKey":"display-1-com","methodName":"SimulateReceive", "params": ["\\x05\\x06taco\\xAA"]}
|
||||
|
||||
devjson:1 {"deviceKey":"display-1","methodName":"InputHdmi1", "params": []}
|
||||
|
||||
devjson:1 {"deviceKey":"display-1","methodName":"PowerOff", "params": []}
|
||||
|
||||
devjson:1 {"deviceKey":"timer","methodName":"Start" }
|
||||
|
||||
devjson:1 {"deviceKey":"timer","methodName":"Cancel" }
|
||||
|
||||
devjson:1 {"deviceKey":"timer","methodName":"Reset" }
|
||||
|
||||
devjson:1 {"deviceKey":"room1","methodName":"Shutdown" }
|
||||
|
||||
devjson:1 {"deviceKey":"mockVc-1", "methodName":"TestIncomingVideoCall", "params": ["123-456-7890"]}
|
||||
|
||||
devjson:1 {"deviceKey":"mockVc-1", "methodName":"TestIncomingAudioCall", "params": ["111-111-1111"]}
|
||||
|
||||
devjson:1 {"deviceKey":"mockVc-1", "methodName":"TestIncomingVideoCall", "params": ["444-444-4444"]}
|
||||
|
||||
devjson:1 {"deviceKey":"mockVc-1", "methodName":"ListCalls"}
|
||||
|
||||
devjson:1 {"deviceKey":"room1-emergency", "methodName":"RunEmergencyBehavior"}
|
||||
|
||||
devjson:1 {"deviceKey":"microphonePrivacyController-1", "methodName":"TogglePrivacyMute"}
|
||||
|
||||
devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"SetTestValue", "params": [ true ]}
|
||||
|
||||
devjson:1 {"deviceKey":"room1.InCallFeedback","methodName":"ClearTestValue", "params": []}
|
||||
|
||||
devjson:3 {"deviceKey":"room1.RoomOccupancy.RoomIsOccupiedFeedback","methodName":"SetTestValue", "params": [ true ]}
|
||||
|
||||
devjson:2 {"deviceKey":"codec-comms-ssh", "methodName":"SendText", "params": ["xcommand dial number: 10.11.50.211\r"]}
|
||||
|
||||
devjson:2 {"deviceKey":"codec-comms-ssh", "methodName":"Connect", "params": []}
|
||||
|
||||
devjson:1 {"deviceKey":"commBridge", "methodName":"ExecuteJoinAction", "params":[ 301, "digital", true ]}
|
||||
|
||||
devjson:2 {"deviceKey":"display01Comm-com", "methodName":"SendText", "params": [ "I'M GETTING TIRED OF THIS" ]}
|
||||
|
||||
devjson:10 {"deviceKey":"dmLink-ssh", "methodName":"Connect", "params": []}
|
||||
|
||||
devjson:2 {"deviceKey":"roomCombiner", "methodName":"SetRoomCombinationScenario", "params": ["combined"]}
|
||||
|
||||
devjson:2 {"deviceKey":"roomCombiner", "methodName":"SetRoomCombinationScenario", "params": ["divided"]}
|
||||
58
docs/docfx.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json",
|
||||
"metadata": [
|
||||
{
|
||||
"src": [
|
||||
{
|
||||
"src": "../",
|
||||
"files": [
|
||||
"src/**/*.csproj"
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"TargetFramework": "net472"
|
||||
},
|
||||
"dest": "api",
|
||||
"namespaceLayout": "nested",
|
||||
"outputFormat": "apiPage"
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"docs/**/*.{md,yml}",
|
||||
"api/**/*.{md,yml}",
|
||||
"index.md",
|
||||
"toc.yml"
|
||||
],
|
||||
"exclude": [
|
||||
"_site/**",
|
||||
".github/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"docs/images/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"output": "_site",
|
||||
"template": [
|
||||
"default",
|
||||
"modern"
|
||||
],
|
||||
"globalMetadata": {
|
||||
"_appName": "PepperDash Essentials",
|
||||
"_appTitle": "PepperDash Essentials",
|
||||
"_enableSearch": true,
|
||||
"_appLogoPath": "docs/images/favicon-32x32.png",
|
||||
"_appFaviconPath": "docs/images/favicon.ico",
|
||||
"_disableToc": false,
|
||||
"pdf": false
|
||||
}
|
||||
}
|
||||
}
|
||||
41
docs/docs/Arch-1.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Essentials architecture
|
||||
|
||||
## Device and DeviceManager
|
||||
|
||||
---
|
||||
[YouTube Video - The Device Model in PepperDash Essentials](https://youtu.be/QF4vCQfOYGw)
|
||||
***
|
||||
|
||||
A `Device` (`PepperDash.Core.Device`) is a logical construct. It may represent a piece of hardware, a port, a socket, a collection of other devices/ports/constructs that define an operation, or any unit of logic that should be created at startup and exist independent of other devices.
|
||||
|
||||
`DeviceManager` (`PepperDash.Essentials.Core.DeviceManager`) is the collection of all Devices. The collection of everything we control, and other business logic in a system. See the list below for what is typical in the device manager.
|
||||
|
||||
## Flat system design
|
||||
|
||||
In Essentials, most everything we do is focused in one layer: The Devices layer. This layer interacts with the physical Crestron and other hardware and logical constructs underneath, and is designed so that we rarely act directly on the often-inconsistent hardware layer. The `DeviceManager` is responsible for containing all of the devices in this layer.
|
||||
|
||||
Types of things in `DeviceManager`:
|
||||
|
||||
* Rooms
|
||||
* Sources
|
||||
* Codecs, DSPs, displays, routing hardware
|
||||
* IR Ports, Com ports, SSh Clients, ...
|
||||
* Occupancy sensors and relay-driven devices
|
||||
* Logical devices that manage multiple devices and other business, like shade or lighting scene controllers
|
||||
* Fusion connectors to rooms
|
||||
|
||||
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
|
||||
|
||||
> The `DeviceManager` is nothing more than a modified collection of things, and technically those things don't have to be Devices, but must at least implement the `IKeyed` (`PepperDash.Core.IKeyed`) interface (simply so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of [activation](~/docs/Arch-activate.md#2-pre-activation) at startup. This collection of devices is all interrelated by their string keys.
|
||||
|
||||
In this flat design, we spin up devices, and then introduce them to their "coworkers and bosses" - the other devices and logical units that they will interact with - and get them all operating together to form a running unit. For example: A room configuration will contain a "VideoCodecKey" property and a "DefaultDisplayKey" property. The `DeviceManager` provides the room with the codec or displays having the appropriate keys. What the room does with those is dependent on its coding.
|
||||
|
||||
> In the default Essentials routing scheme, the routing system gets the various devices involved in given route from `DeviceManager`, as they are discovered along the defined tie-lines. This is all done at route-time, on the fly, using only device and port keys. As soon as the routing operation is done, the whole process is released from memory. This is extremely-loose coupling between objects.
|
||||
|
||||
This flat structure ensures that every device in a system exists in one place and may be shared and reused with relative ease. There is no hierarchy.
|
||||
|
||||
## Architecture drawing
|
||||
|
||||

|
||||
|
||||
Next: [Configurable lifecycle](~/docs/Arch-lifecycle.md)
|
||||
158
docs/docs/Arch-activate.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Essentials architecture: DeviceManager activation
|
||||
|
||||
## What is all this?
|
||||
|
||||
The Essentials system architecture is a loose collection of "things" - generally real or logical Devices - that all need to relate to each other. In the interest of keeping Essentials extensible and flexible, we use an non-ordered collection of objects that should only have references to each other in the least-binding way possible. Meaning: Devices should be designed to be able to function without related objects present, and when they are present they should only retain loose reference to those other objects for memory management and later deconstruction as Essentials grows into a real-time configurable environment.
|
||||
|
||||
In order to facilitate this loose coupling, Essentials devices go through five phases during the startup process: Construction; addition to `DeviceManager`; pre-activation; activation; post-activation. We will describe what is optimal behavior for each of the steps below:
|
||||
|
||||
### Classes Referenced
|
||||
|
||||
* `PepperDash.Core.Device`
|
||||
* `PepperDash.Essentials.Core.EssentialsDevice`
|
||||
* `PepperDash.Essentials.Core.DeviceManager`
|
||||
* `PepperDash.Essentials.Core.Privacy.MicrophonePrivacyController`
|
||||
|
||||
## 1. Construction and addition to the DeviceManager
|
||||
|
||||
In general, a device's constructor should only be used to get the "framework" of the device in place. All devices are constructed in this stage. Rooms and fusion bridges included. Simple devices like IR driver devices, and devices with no controls can be completely spun up in this phase. All devices are added to the `DeviceManager` after they are constructed, but may not be fully functional.
|
||||
|
||||
## 2. Pre-activation
|
||||
|
||||
This stage is rarely used. It exists to allow an opportunity for any necessary logic to take place before the main activation phase.
|
||||
|
||||
## 3. Activation
|
||||
|
||||
This stage is the main phase of startup, and where most devices will get up and running, if they need additional startup behavior defined. The developer will code an optional overridden `CustomActivate()` method on the device class. This is where hardware ports may be set up; signals and feedbacks linked; UI drivers fired up; rooms linked to their displays and codec... With the exception of early-designed devices, most new Essentials classes do all of their startup here, rather than in their constructors.
|
||||
|
||||
Remember that in your `CustomActivate()` method, you cannot assume that a device you depend on is alive and running yet. It may be activating later. You _can_ depend on that device's existence, and link yourself to it, but it may not be functional yet. In general, there should be no conditions in any Essentials code that depend on device startup sequence and ordering. All rooms, devices, classes should be able to function without linked devices being alive, and respond appropriately when they do come to life. Any post-activation steps can be done in step four below - and should be avoided in general.
|
||||
|
||||
If the `CustomActivate()` method is long, consider breaking it up into many smaller methods. This will enhance exception handling and debugging when things go wrong, with more-detailed stack traces, and makes for easier-to-read code.
|
||||
|
||||
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()
|
||||
{
|
||||
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks");
|
||||
SetupFunctions();
|
||||
SetupFeedbacks();
|
||||
|
||||
EISC.SigChange += EISC_SigChange;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Post-activation
|
||||
|
||||
This phase is used primarily to handle any logic in a device that might be dependent on another device, and we need to ensure that we have waited for the dependent device to be activated first. For example, if we look at the `MicrophonePrivacyController` class, this is a "virtual" device whose purpose is to control the mute state of microphones from one or more contact closure inputs as well as provide feedback via different colored LEDs as to the current mute state. This virtual-device doesn't actually represent any sort of physical hardware device, but rather relies on associating itself with other devices that represent digital inputs and relays as well as whatever device is responsible for preforming the actual muting of the microphones.
|
||||
|
||||
We can see in the example below that during the `CustomActivate()` phase, we define a post-activation action via a lambda in `AddPostActivationAction()` that will execute during the post-activation phase. The purpose here is to check the state of the microphone mute and set the state of the relays that control the LEDs accordingly. We need to do this as a post-activation action because we need to make sure that the devices PrivacyDevice, RedLedRelay and GreenLedRelay are fully activated before we can attempt to interact with them.
|
||||
|
||||
### **Example**
|
||||
|
||||
```cs
|
||||
public override bool CustomActivate()
|
||||
{
|
||||
foreach (var i in Config.Inputs)
|
||||
{
|
||||
var input = DeviceManager.GetDeviceForKey(i.DeviceKey) as IDigitalInput;
|
||||
if(input != null)
|
||||
AddInput(input);
|
||||
}
|
||||
|
||||
var greenLed = DeviceManager.GetDeviceForKey(Config.GreenLedRelay.DeviceKey) as GenericRelayDevice;
|
||||
if (greenLed != null)
|
||||
GreenLedRelay = greenLed;
|
||||
else
|
||||
Debug.Console(0, this, "Unable to add Green LED device");
|
||||
|
||||
var redLed = DeviceManager.GetDeviceForKey(Config.RedLedRelay.DeviceKey) as GenericRelayDevice;
|
||||
if (redLed != null)
|
||||
RedLedRelay = redLed;
|
||||
else
|
||||
Debug.Console(0, this, "Unable to add Red LED device");
|
||||
|
||||
AddPostActivationAction(() => {
|
||||
CheckPrivacyMode();
|
||||
PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange -= PrivacyModeIsOnFeedback_OutputChange;
|
||||
PrivacyDevice.PrivacyModeIsOnFeedback.OutputChange += PrivacyModeIsOnFeedback_OutputChange;
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
|
||||
return base.CustomActivate();
|
||||
}
|
||||
|
||||
void CheckPrivacyMode()
|
||||
{
|
||||
if (PrivacyDevice != null)
|
||||
{
|
||||
var privacyState = PrivacyDevice.PrivacyModeIsOnFeedback.BoolValue;
|
||||
if (privacyState)
|
||||
TurnOnRedLeds();
|
||||
else
|
||||
TurnOnGreenLeds();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Activation exceptions
|
||||
|
||||
Each of the three activation phases operates in a try/catch block for each device. This way if one device has a fatal failure during activation, the failure will be logged and the system can continue to activate. This allows the developer to chase down multiple issues per load while testing, or to fix configuration omissions/errors as a group rather than one-at-a-time. A program can theoretically be fully-initialized and have many or all devices fail. We generally do not want to depend on exception handling to log device failures. Construction and activation code should have plenty of null checks, parameter validity checks, and debugging output to prevent exceptions from occurring. `String.IsEmptyOrNull(myString)` and `if(myObject == null)` are your friends. Invite them often.
|
||||
|
||||
## Interdependence
|
||||
|
||||
In any real-world system, devices and business logic need to talk to each other, otherwise, what's the point of all this coding? When creating your classes and configuration, it is best practice to _try_ not to "plug" one device into another during construction or activation. For example your touchpanel controller class has a `Display1` property that holds the display-1 object. Rather, it may be better to refer to the device as it is stored in the `DeviceManager` when it's needed using the static `DeviceManager.GetDeviceForKey(key)` method to get a reference to the device, which can be cast using various interfaces/class types, and then interacted with. This prevents objects from being referenced in places where the developer may later forget to dereference them, causing memory leak. This will become more important as Essentials becomes more able to be reconfigured at runtime.
|
||||
|
||||
As an example, [connection-based routing](~/docs/Connection-based-routing.md#essentials-connection-based-routing) uses these methods. When a route is requested, the collection of tielines and devices is searched for the devices and paths necessary to complete a route, but there are no devices or tie lines that are object-referenced in running code. It can all be torn down and reconfigured without any memory-management dereferencing, setting things to null.
|
||||
|
||||
## Device Initialization
|
||||
|
||||
Once the `DeviceManager` has completed the activation phase cycle for all devices, the devices themselves can be initialized. The `EssentialsDevice` class subscribes to the `DeviceManager.AllDevicesActivated` event and invokes the virtual `Initialize()` method on `Device` in a separate thread. This allows all devices to concurrently initialize in parallel threads.
|
||||
|
||||
The main task that should be undertaken in the `Initialize()` method for any 3rd party device class, it to begin communication with the device via its API. Ideally, no class that communicates with a 3rd party device outside the program should attempt to start communicating before this point.
|
||||
|
||||
### Example (from `PepperDash.Essentials.Devices.Common.VideoCodec.Cisco.CiscoSparkCodec`)
|
||||
```cs
|
||||
public override void Initialize()
|
||||
{
|
||||
var socket = Communication as ISocketStatus;
|
||||
if (socket != null)
|
||||
{
|
||||
socket.ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(socket_ConnectionChange);
|
||||
}
|
||||
|
||||
Communication.Connect();
|
||||
|
||||
CommunicationMonitor.Start();
|
||||
|
||||
const string prefix = "xFeedback register ";
|
||||
|
||||
CliFeedbackRegistrationExpression =
|
||||
prefix + "/Configuration" + Delimiter +
|
||||
prefix + "/Status/Audio" + Delimiter +
|
||||
prefix + "/Status/Call" + Delimiter +
|
||||
prefix + "/Status/Conference/Presentation" + Delimiter +
|
||||
prefix + "/Status/Cameras/SpeakerTrack" + Delimiter +
|
||||
prefix + "/Status/RoomAnalytics" + Delimiter +
|
||||
prefix + "/Status/RoomPreset" + Delimiter +
|
||||
prefix + "/Status/Standby" + Delimiter +
|
||||
prefix + "/Status/Video/Selfview" + Delimiter +
|
||||
prefix + "/Status/Video/Layout" + Delimiter +
|
||||
prefix + "/Status/Video/Input/MainVideoMute" + Delimiter +
|
||||
prefix + "/Bookings" + Delimiter +
|
||||
prefix + "/Event/CallDisconnect" + Delimiter +
|
||||
prefix + "/Event/Bookings" + Delimiter +
|
||||
prefix + "/Event/CameraPresetListUpdated" + Delimiter +
|
||||
prefix + "/Event/UserInterface/Presentation/ExternalSource/Selected/SourceIdentifier" + Delimiter;
|
||||
}
|
||||
```
|
||||
|
||||
## The goal
|
||||
|
||||
Robust C#-based system code should not depend on "order" or "time" to get running. We do not need to manage the order of our startup in this environment. Our Room class may come alive before our DSP and or Codec, and the Room is responsible for handling things when those devices become available. The UI layer is responsible for blocking the UI or providing status when the Room's requirements are coming alive, or if something has gone away. We use events or `Feedbacks` to notify dependents that other devices/classes are ready or not, but we do not prevent continued construction/activation of the system when many of these events don't happen, or don't happen in a timely fashion. This removes the need for startup management, which is often prolonged and consumes _tons_ of developer/installer time. A fully-loaded Essentials system may go through activation in several seconds, with all devices concurrently getting themselves going, where legacy code may take 10 minutes.
|
||||
|
||||
When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future.
|
||||
|
||||
Next: [More architecture](~/docs/Arch-topics.md)
|
||||
9
docs/docs/Arch-lifecycle.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Essentials Configurable System Lifecycle
|
||||
|
||||
The diagram below describes how Essentials gets a program up and running.
|
||||
|
||||
(The various activation phases are covered in more detail on the [next page](~/docs/Arch-activate.md))
|
||||
|
||||

|
||||
|
||||
Next: [Activation phases](~/docs/Arch-activate.md)
|
||||
19
docs/docs/Arch-summary.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Essentials architecture
|
||||
|
||||
## Summary
|
||||
|
||||
PepperDash Essentials is an open-source framework for control systems, built on Crestron's Simpl# Pro framework. It can be configured as a standalone program capable of running a wide variety of system designs and can also be used to augment other Crestron programs.
|
||||
|
||||
Essentials is a collection of C# libraries that can be used in many ways. It is a 100% configuration-driven framework that can be extended to add different workflows and behaviors, either through the addition of new device-types and classes, or via a plug-in mechanism. The framework is a collection of things that are all related and interconnected, but in general do not have strong dependencies on each other.
|
||||
|
||||
## Framework Libraries
|
||||
|
||||
The table below is a guide to understand the basic organization of code concepts within the various libraries that make up the architecture.
|
||||
|
||||

|
||||
|
||||
The diagram below shows the reference dependencies that exist between the different component libraries that make up the Essentials Framework.
|
||||
|
||||

|
||||
|
||||
Next: [Architecture](~/docs/Arch-1.md)
|
||||
156
docs/docs/Arch-topics.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Configuration topics
|
||||
|
||||
Configuration is central to Essentials. On this page we will cover configuration-related topics, including the important concept of configure-first and some details about the config file process.
|
||||
|
||||
## Classes Referenced
|
||||
|
||||
- `PepperDash.Essentials.Core.Config.DeviceConfig`
|
||||
|
||||
## Configure-first development
|
||||
|
||||
## Framework Libraries
|
||||
|
||||
The table below is meant to serve as a guide to understand the basic organization of code concepts within the various libraries that make up the architecture.
|
||||
|
||||
_Todo, try a text-based table:_
|
||||
|
||||

|
||||
|
||||
The diagram below shows the reference dependencies that exist between the different component libraries that make up the Essentials Framework.
|
||||
|
||||

|
||||
|
||||
### Architecture
|
||||
|
||||
#### Device and DeviceManager
|
||||
|
||||
A `Device` is a logical construct. It may represent a piece of hardware, a port, a socket, a collection of other devices/ports/constructs that define an operation, or any unit of logic that should be created at startup and exist independent of other devices.
|
||||
|
||||
`DeviceManager` is the collection of all Devices. The collection of everything we control on a system. **ADD SOME MORE HERE**
|
||||
|
||||
#### Flat system design
|
||||
|
||||
In Essentials, most everything we do is focused in one layer: The Devices layer. This layer interacts with the physical Crestron and other hardware and logical constructs underneath, and is designed so that we rarely act directly on the often-inconsistent hardware layer. The `DeviceManager` is responsible for containing all of the devices in this layer.
|
||||
|
||||
Types of devices:
|
||||
|
||||
- Rooms
|
||||
- Sources
|
||||
- Codecs, DSPs, displays, routing hardware
|
||||
- IR Ports, Com ports, SSh Clients, ...
|
||||
- Occupancy sensors and relay-driven devices
|
||||
- Logical devices that manage multiple devices and other business, like shade or lighting scene controllers
|
||||
- Fusion connectors to rooms
|
||||
|
||||
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
|
||||
|
||||
> The `DeviceManager` is a modified collection of objects, and those objects don't have to inherit from Devices or EssentialsDevices, but must at least implement the `IKeyed` interface (so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of activation at startup. This collection of devices is all interrelated by their string keys.
|
||||
|
||||
In this flat design, we spin up devices, and then introduce them to their "coworkers and bosses" - the other devices and logical units that they will interact with - and get them all operating together to form a running unit. For example: A room configuration will contain a "VideoCodecKey" property and a "DefaultDisplayKey" property. The `DeviceManager` provides the room with the codec or displays having the appropriate keys. What the room does with those is dependent on its coding.
|
||||
|
||||
> In the default Essentials routing scheme, the routing system gets the various devices involved in given route from `DeviceManager`, as they are discovered along the defined tie-lines. This is all done at route-time, on the fly, using only device and port keys. As soon as the routing operation is done, the whole process is released from memory. This is extremely-loose coupling between objects.
|
||||
|
||||
This flat structure ensures that every device in a system exists in one place and may be shared and reused with relative ease. There is no hierarchy.
|
||||
|
||||
#### Architecture drawing
|
||||
|
||||

|
||||
|
||||
#### Essentials Configurable System Lifecycle
|
||||
|
||||

|
||||
|
||||
### Activation phases additional topics and examples (OTHER DOCS)
|
||||
|
||||
Concepts (link)
|
||||
|
||||
Room and touchpanel activation (link)
|
||||
|
||||
#### Configure first development
|
||||
|
||||
One of the primary concepts that has been adopted and must be adhered to when writing for Essentials framework is the concept of "configure first." The simple version is: Write what you need to do in the related configuration file (and configuration tool) first, then write the code that runs from that configuration. This ensures that the running code can actually be configured in the "flat" structure of devices and rooms that Essentials uses.
|
||||
|
||||
Often, code is written and tested first without consideration for configurability. Then, when a developer tries to make it configurable, they discover that the code as written doesn’t support it without complicated configuration files. This creates spaghetti code in tools that are written to generate configurations and tends to create tighter coupling between objects than we desire. Later, a modified version of the original program is desired, but because the code was written in such a specific fashion, the code is hard to refactor and extend. This causes the configuration tool and configuration files to become even more convoluted. The modern versions of configuration tools that are starting to come out are modular and componentized. We want to ensure as much re-use of these modules as possible, with extensions and added features added on, rather than complete rewrites of existing code. In our running systems, we want to ensure as much flexibility in design as possible, eliminating multiple classes with similar code.
|
||||
|
||||
### Configuration reader process
|
||||
|
||||
At the heart of the Essentials framework is the configuration system. While not technically necessary for a system written with the Essentials framework, it is the preferred and, currently, the only way to build an Essentials system. The configuration file is JSON, and well-defined (but not well documented, yet). It is comprised of blocks:
|
||||
|
||||
- info (object) Contains metadata about the config file
|
||||
- devices (array) Contains, well, the devices we intend to build and load
|
||||
- rooms (array, typically only one) Contains the rooms we need
|
||||
- sourceLists (object) Used by one or more rooms to represent list(s) of sources for those rooms
|
||||
- tieLines (array) Used by the routing system to discover routing between sources and displays
|
||||
|
||||
In addition, a downloaded Portal config file will most likely be in a template/system form, meaning that the file contains two main objects, representing the template configuration and its system-level overrides. Other metadata, such as Portal UUIDs or URLs may be present.
|
||||
|
||||
At startup, the configuration file is read, and a ReadyEvent is fired. Upon being ready, that configuration is loaded by the ConfigReader.LoadConfig() method. The template and system are merged into a single configuration object, and that object is then deserialized into configuration wrapper classes that define most of the structure of the program to be built. (Custom configuration objects were built to allow for better type handling rather than using JToken methods to parse out error-prone property names.)
|
||||
|
||||
For example, a `DeviceConfig` object:
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
public class DeviceConfig
|
||||
{
|
||||
[JsonProperty("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
[JsonProperty("uid")]
|
||||
public int Uid { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("group")]
|
||||
public string Group { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonProperty("properties")]
|
||||
[JsonConverter(typeof(DevicePropertiesConverter))]
|
||||
public JToken Properties { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
_Every_ `Device` present must adhere to those five properties plus a properties object. The properties object will have its own deserialization helpers, depending on what its structure is.
|
||||
|
||||
Once the ConfigReader has successfully read and deserialized the config file, then `ControlSystem.Load()` is called. This does the following in order:
|
||||
|
||||
1. Loads Devices
|
||||
2. Loads TieLines
|
||||
3. Loads Rooms
|
||||
4. Loads LogoServer
|
||||
5. Activation sequence
|
||||
|
||||
This ordering ensures that all devices are at least present before building tie lines and rooms. Rooms can be built without their required devices being present. In principle, this could break from the loosely-coupled goal we have described, but it is the clearest way to build the system in code. The goal is still to build a room class that doesn't have functional dependencies on devices that may not be ready for use.
|
||||
|
||||
In each device/room step, a device factory process is called. We call subsequent device factory methods in the various libraries that make up Essentials until one of them returns a functional device. This allows us to break up the factory process into individual libraries, and not have a huge list of types and build procedures. Here's part of the code:
|
||||
|
||||
```cs
|
||||
// Try local factories first
|
||||
var newDev = DeviceFactory.GetDevice(devConf);
|
||||
|
||||
if (newDev == null)
|
||||
newDev = BridgeFactory.GetDevice(devConf);
|
||||
|
||||
// Then associated library factories
|
||||
if (newDev == null)
|
||||
newDev = PepperDash.Essentials.Core.DeviceFactory.GetDevice(devConf);
|
||||
if (newDev == null)
|
||||
newDev = PepperDash.Essentials.Devices.Common.DeviceFactory.GetDevice(devConf);
|
||||
if (newDev == null)
|
||||
newDev = PepperDash.Essentials.DM.DeviceFactory.GetDevice(devConf);
|
||||
if (newDev == null)
|
||||
newDev = PepperDash.Essentials.Devices.Displays.DisplayDeviceFactory.GetDevice(devConf);
|
||||
```
|
||||
|
||||
In each respective factory, or device constructor, the configuration's properties object is either converted to a config object or read from using `JToken` methods. This builds the device which may be ready to go, or may require activation as described above.
|
||||
|
||||
A similar process is carried out for rooms, but as of now, the room types are so few that they are all handled in the `ControlSystem.LoadRooms()` method.
|
||||
|
||||
_This process will soon be enhanced by a plug-in mechanism that will drill into dynamically-loaded DLLs and load types from factories in those libraries. This is where custom essentials systems will grow from._
|
||||
|
||||
After those five steps, the system will be running and ready to use.
|
||||
15
docs/docs/Bridging-To-Hardware-Resources.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Bridging to Hardware and Network Resources
|
||||
|
||||
One of the most powerful features of Essentials is the ability to bridge SIMPL to and hardware resource to any piece of equipment instantiated inside of essentials. You can bridge directly to a comport on the processor just as easily as you can to the comport of an instantiated DM device. A simple change in the connection location of a display can be made with just a few keystrokes. This isn't restricted to comports either. Devices and direct connections can be linked to Digital Inputs, IR Ports, Relays, Comports, SSH, TCP/IP, UDP, and Cresnet resources.
|
||||
|
||||
## Examples
|
||||
|
||||
Follow the links below for examples of bridging to hardware and network resources.
|
||||
|
||||
**[GenericComm Bridging](~/docs/GenericComm.md)**
|
||||
|
||||
**[RelayOutput Bridging](~/docs/RelayOutput.md)**
|
||||
|
||||
**[Digital Input Bridging](~/docs/DigitalInput.md)**
|
||||
|
||||
**[Card Frame Bridging](~/docs/CardFrame.md)**
|
||||
14
docs/docs/CardFrame.md
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"key": "cardCage1",
|
||||
"uid": 1,
|
||||
"name": "Internal Card Cage",
|
||||
"type": "internalcardcage",
|
||||
"group": "cardCage",
|
||||
"properties": {
|
||||
"cards": {
|
||||
"1": "c3com3",
|
||||
"2": "c3com3",
|
||||
"3": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
75
docs/docs/Communication-Basics.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Unifying communication methods
|
||||
|
||||
In networked A/V systems, devices can use many different methods of communication: COM ports, TCP/IP sockets, Telnet, SSH. Generally, the data protocol and commands that are sent and received using any of these methods are the same, and it is not necessary for a device to know the details of the communication method it is using. A Samsung MDC protocol display in room 1 using RS232 speaks the same language as another Samsung MDC does in the next room using TCP/IP. For these, and most cases where the device doesn't need to know its communication method, we introduce the `IBasicCommunication` interface.
|
||||
## Classes Referenced
|
||||
|
||||
* `PepperDash.Core.IBasicCommunication`
|
||||
* `PepperDash.Core.ISocketStatus`
|
||||
* `PepperDash.Core.GenericTcpIpClient`
|
||||
* `PepperDash.Core.GenericSshClient`
|
||||
* `PepperDash.Core.GenericSecureTcpIpClient`
|
||||
* `PepperDash.Essentials.Core.ComPortController`
|
||||
* `PepperDash.Essentials.Core.StatusMonitorBase`
|
||||
## IBasicCommunication and ISocketStatus
|
||||
|
||||
All common communication controllers will implement the `IBasicCommunication` interface, which is an extension of `ICommunicationReceiver`. This defines receive events, connection state properties, and send methods. Devices that need to use COM port, TCP, SSh or other similar communication will require an `IBasicCommunication` type object to be injected at construction time.
|
||||
|
||||
```cs
|
||||
/// <summary>
|
||||
/// An incoming communication stream
|
||||
/// </summary>
|
||||
public interface ICommunicationReceiver : IKeyed
|
||||
{
|
||||
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
bool IsConnected { get; }
|
||||
void Connect();
|
||||
void Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device that uses basic connection
|
||||
/// </summary>
|
||||
public interface IBasicCommunication : ICommunicationReceiver
|
||||
{
|
||||
void SendText(string text);
|
||||
void SendBytes(byte[] bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||
/// GenericTcpIpClient
|
||||
/// </summary>
|
||||
public interface ISocketStatus : IBasicCommunication
|
||||
{
|
||||
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### Developing devices with communication
|
||||
|
||||
Essentials uses dependency injection concepts in its start up phase. Simply, most devices use the same methods of communication, and are often communication-agnostic. During the build-from-configuration phase, the communication method device is instantiated, and then injected into the device that will use it. Since the communication device is of `IBasicCommunication`, the device controller receiving it knows that it can do things like listen for events, send text, or be notified when sockets change.
|
||||
|
||||
### Device Factory, Codec example
|
||||
|
||||

|
||||
|
||||
The DeviceManager will contain two new devices after this: The Cisco codec, and the codec's `GenericSshClient`. This enables easier debugging of the client using console methods. Some devices like this codec will also have a `StatusMonitorBase` device, for Fusion and other reporting.
|
||||
|
||||
> `ComPortController` is `IBasicCommunication` as well, but methods like `Connect()` and `Disconnect()` do nothing on these types.
|
||||
|
||||
#### ISocketStatus
|
||||
|
||||
`PepperDash.Core.GenericTcpIpClient`, `GenericSshClient` and some other socket controllers implement `ISocketStatus`, which is an extension of `IBasicCommunication`. This interface reveals connection status properties and events.
|
||||
|
||||
```cs
|
||||
public interface ISocketStatus : IBasicCommunication
|
||||
{
|
||||
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
```
|
||||
|
||||
Classes that are using socket-based comms will need to check if the communication is `ISocketStatus` and link up to the `ConnectionChange` event for connection handling.
|
||||
291
docs/docs/ConfigurationStructure.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# Configuration Structure
|
||||
|
||||
---
|
||||
|
||||
[YouTube Video - Configuring PepperDash Essentials](https://youtu.be/EK8Ti9a1o7s)
|
||||
|
||||
***
|
||||
|
||||
The Essentials configuration structure is designed to allow minimum duplication of data across systems that share many similarities, which makes it ideally suited for applications where large numbers of duplicate room types must be deployed.
|
||||
|
||||
At a high level, the idea is to define a template of all of the common configuration shared by a group of systems of the same type. Then individual differences per system instance can be defined in a system block that either add data missing in the template, or override the default values set in the template.
|
||||
|
||||
## Top Level Object Structure (Double Config)
|
||||
|
||||
```cs
|
||||
{
|
||||
// This object is deserialized to type PepperDash.Essentials.Core.Config.EssentialsConfig
|
||||
|
||||
"system_url":"", // For Portal use only
|
||||
"template_url":"", // For Portal use only
|
||||
"template":{
|
||||
// This object is deserialized to type PepperDash.Essentials.Core.EssentialsConfig
|
||||
// For most manually generated configuration, only define data here. Leave system empty
|
||||
},
|
||||
"system":{
|
||||
// This object is deserialized to type PepperDash.Essentials.Core.EssentialsConfig
|
||||
// Any data here will be overlayed on top of the data in template. In the case of duplicate values
|
||||
// the value in system will be overwrite any value in template
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Object Structure for `template` and `system` (`PepperDash.Essentials.Core.EssentialsConfig`)
|
||||
|
||||
``` js
|
||||
{
|
||||
"info": {
|
||||
// This object is deserialized to type PepperDash.Essentials.Core.Config.InfoConfig
|
||||
// Contains information about the system/configuration
|
||||
},
|
||||
"devices": [
|
||||
// This object is deserialized to type List<PepperDash.Essentials.Core.Config.DeviceConfig>
|
||||
// An array of devices
|
||||
],
|
||||
"rooms": [
|
||||
// This object is deserialized to type List<PepperDash.Essentials.Core.Config.DeviceConfig>
|
||||
// An array of rooms. These are not automatically deserialized
|
||||
],
|
||||
"tielines":[
|
||||
// An array of tie lines that describe the connections between routing ports on devices
|
||||
],
|
||||
"sourceLists":{
|
||||
// This object is deserialized to type Dictionary<string, Dictionary<string, PepperDash.Essentials.Core.SourceListItem>>
|
||||
// An object that contains a collection
|
||||
},
|
||||
"joinMaps":{
|
||||
// This object is deserialized to type Dictionary<string, string> where the value is a serialized class that inherits from JoinMapBase to be deserialized later
|
||||
// Used to define custom join maps for bridging devices to SIMPL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## The Template and System Concept (Merging Configurations)
|
||||
|
||||
In order to understand how and why we use a double configuration concept, it's important to understand the relationship between a Template and a System in Portal. A System represents a physical installed group of hardware(either currently or in the future), acting together usually as part of a single control system program. A system MUST inherit from a Template. A Template represents the common elements of one or more systems.
|
||||
|
||||
The idea being that configuration values that are common to all systems can be stored in the configuration for the template. Then, any configuration values that are unique to a particular system cane be stored in the configuration of the System. By "merging" the System configuration values over top of the Template configuration values, the resulting data contains all of the values that should be shared by each system that inherits from a common template, as well as the unique values for each individual system.
|
||||
|
||||
Below is an example of a double configuration containing both template and system properties.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"info": {
|
||||
"name": "Template Name",
|
||||
"description": "A 12 person conference room"
|
||||
},
|
||||
"devices": [
|
||||
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
"info": {
|
||||
"name": "System Name",
|
||||
"myNewSystemProperty": "Some Value"
|
||||
},
|
||||
"devices": [
|
||||
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Below is an example of the result of merging the above double configuration example into a single configuration.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"info": {
|
||||
"name": "System Name", // Since this property existed in both the template and system, the system value replaces the template value after the merge
|
||||
"description": "A 12 person conference room", // This property existed only in the template and is unchanged after the merge
|
||||
"myNewSystemProperty": "Some Value" // This property existed only in the system and is unchanged after the merge
|
||||
},
|
||||
"devices": [
|
||||
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device Object Structure
|
||||
|
||||
The devices array is meant to hold a series of device objects. The basic device object structure is defined below.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"key": "someUniqueString", // *required* a unique string
|
||||
"name": "A friendly Name", // *required* a friendly name meant for display to users
|
||||
"type": "exampleType", // *required* the type identifier for this object.
|
||||
"group": "exampleGroup", // *required* the group identifier for this object. This really equates to a category for the device,
|
||||
// such as "lighting" or "displays" and may be deprecated in future in favor of "category"
|
||||
"uid":0, // *required* a unique numeric identifier for each device
|
||||
"properties": { // *required* an object where the configurable properties of the device are contained
|
||||
"control": { // an object to contain all of the properties to connect to and control the device
|
||||
"method": "ssh", // the control method used by this device
|
||||
"tcpSshProperties": { // contains the necessary properties for the specified method
|
||||
"address": "1.2.3.4",
|
||||
"port": 22,
|
||||
"username": "admin",
|
||||
"password": "uncrackablepassword"
|
||||
}
|
||||
},
|
||||
"someCustomProperty": "I Love Tacos!"
|
||||
}
|
||||
// Do NOT add any custom data at the top level of the device object. All custom data must be in the properties object.
|
||||
}
|
||||
```
|
||||
|
||||
Some additional details about specific properties that are important to note:
|
||||
|
||||
* "key": This value needs to be unique in the array of devices objects
|
||||
* "uid": This value also needs to be unique for reasons related to configuration tools and template/system merging
|
||||
* "type": Think of this as a way to identify what specific module you might associate with this device. In Essentials, this value is used to determine what class will be instantiated for the device (ex. "necmpsx" or "samsungMdc" for two types of displays)
|
||||
* "properties": This object is used to store both specific and miscellaneous data about the device.
|
||||
* Specific data, like that shown above in the "control" object has a pre-defined structure.
|
||||
* Other data must be stored as objects or new properties inside the "properties" object such as "someCustomProperty" in the example above.
|
||||
* Do NOT add any additional properties at the top level of the device object. All custom data must be in the "properties" object.
|
||||
|
||||
## The Device Properties.Control Object
|
||||
|
||||
The control object inside properties has some reserved properties that are used by configuration tools and Essentials that require some caution.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"properties": { // *required* an object where the configurable properties of the device are contained
|
||||
"control": { // an object to contain all of the properties to connect to and control the device
|
||||
// Example of the reserved properties for a socket based port (ssh, tcpIp, udp)
|
||||
"method": "ssh", // the control method used by this device
|
||||
"tcpSshProperties": { // contains the necessary properties for the specified method
|
||||
"address": "1.2.3.4", // IP Address or hostname
|
||||
"port": 22,
|
||||
"username": "admin",
|
||||
"password": "uncrackablepassword",
|
||||
"autoReconnect": true, // If true, the client will attempt to re-connect if the connection is broken externally
|
||||
"AutoReconnectIntervalMs": 2000 // The time between re-connection attempts
|
||||
},
|
||||
|
||||
// Example of the reserved properties for a Com port
|
||||
"method": "com",
|
||||
"controlPortNumber": 1, // The number of the com port on the device specified by controlPortDevKey
|
||||
"controlPortDevKey": "processor", // The key of the device where the com port is located
|
||||
"comParams": { // This object contains all of the com spec properties for the com port
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 9600,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device Merging
|
||||
|
||||
The following examples illustrate how the device key and uid properties affect how devices are merged together in a double configuration scenario. In order for a template device and a system device to merge, they must have the same key and uid values
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"info": {
|
||||
"name": "Template Name",
|
||||
"description": "A 12 person conference room"
|
||||
},
|
||||
"devices": [
|
||||
{ // This is the template device
|
||||
"key": "display-1",
|
||||
"name": "Display",
|
||||
"type": "samsungMdc",
|
||||
"group": "displays",
|
||||
"uid":0,
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "ssh",
|
||||
"tcpSshProperties": {
|
||||
"address": "", // Note that at the template level we won't know the actual IP address so this value is left empty
|
||||
"port": 22,
|
||||
"username": "admin",
|
||||
"password": "uncrackablepassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
"info": {
|
||||
"name": "System Name",
|
||||
"myNewSystemProperty": "Some Value"
|
||||
},
|
||||
"devices": [
|
||||
{ // This is the system device
|
||||
"key": "display-1",
|
||||
"uid":0,
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "10.10.10.10" // Note that the actual IP address is specified at the system level
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Below is an example of the result of merging the above double configuration example into a single configuration.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"info": {
|
||||
"name": "System Name",
|
||||
"description": "A 12 person conference room",
|
||||
"myNewSystemProperty": "Some Value"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"key": "display-1",
|
||||
"name": "Display",
|
||||
"type": "samsungMdc",
|
||||
"group": "displays",
|
||||
"uid":0,
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "ssh",
|
||||
"tcpSshProperties": {
|
||||
"address": "10.10.10.10", // Note that the merged device object inherits all of the template
|
||||
// properties and overwrites the template address property with the system value
|
||||
"port": 22,
|
||||
"username": "admin",
|
||||
"password": "uncrackablepassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
|
||||
]
|
||||
}
|
||||
```
|
||||
81
docs/docs/Connection-Based-Routing.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Essentials connection-based routing
|
||||
|
||||
## TL;DR
|
||||
|
||||
Routing is defined by a connection graph or a wiring diagram. Routeable devices are sources, midpoints, or destinations. Devices are connected by tie lines. Tie lines represent the cables connecting devices, and are of type audio, video or both. Routes are made by telling a destination to get an audio/video/combined route from a source.
|
||||
|
||||
## Summary
|
||||
|
||||
Essentials routing is described by defining a graph of connections between devices in a system, typically in configuration. The audio, video and combination connections are like a wiring diagram. This graph is a collection of devices and tie lines, each tie line connecting a source device, source output port, destination device and destination input port. Tie lines are logically represented as a collection.
|
||||
|
||||
When routes are to be executed, Essentials will use this connection graph to decide on routes from source to destination. A method call is made on a destination, which says “destination, find a way for source xyz to get to you.” An algorithm analyzes the tie lines, instantly walking backwards from the destination, down every connection until it finds a complete path from the source. If a connected path is found, the algorithm then walks forward through all midpoints to the destination, executing switches as required until the full route is complete. The developer or configurer only needs to say “destination, get source xyz” and Essentials figures out how, regardless of what devices lie in between.
|
||||
|
||||
### Classes Referenced
|
||||
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingSource`
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingOutputs`
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingInputs`
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingInputsOutputs`
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingSinkNoSwitching`
|
||||
* `PepperDash.Essentials.Core.Routing.IRoutingSinkWithSwitching`
|
||||
|
||||
## Example system, a simple presentation system
|
||||
|
||||
The diagram below shows the connections in a simple presentation system, with a few variations in connection paths. Example routes will be described following the diagram.
|
||||
|
||||
Each visible line between ports on devices represents a tie line. A tie line connects an output port on one device to an input port on another device, for example: an HDMI port on a document camera to an HDMI input on a matrix switcher. A tie line may be audio, video or both. It is essentially a logical representation of a physical cable in a system. This diagram has 12 tie lines, and those tie lines are defined in the tieLines array in configuration.
|
||||
|
||||

|
||||
|
||||
Let’s go through some examples of routing, using pseudo-code:
|
||||
|
||||
1. Method call: “Projector 1, show Doc cam.” Routing will walk backwards through DM-RMC-3 and DM-8x8 iterating through all “wired up” ports until it finds a path back to the Doc cam. Routing will then step back through all devices in the discovered chain, switching routes on those that are switchable: Doc cam: no switching; DM 8x8: route input 3 to output 3; DM-RMC-3: no switching; Projector 1: Select input HDMI In. Route is complete.
|
||||
2. Method call: “Projector 2, show Laptop, video-only.” Routing will walk backwards through DM-RMC-4, DM 8x8, DM-TX-1, iterating through all connected ports until it finds a connection to the laptop. Routing then steps back through all devices, switching video where it can: Laptop: No switching; DM-TX-1: Select HDMI in; DM 8x8: Route input 5 to output 4; DM-RMC-4: No switching; Projector 2: Select HDMI input. Route is complete.
|
||||
3. Method call: “Amplifier, connect Laptop audio.” Again walking backwards to Laptop, as in #2 above. Switching will take place on DM-TX-1, DM 8x8, audio-only.
|
||||
4. Very simple call: “Lobby display, show signage controller.” Routing will walk back on HDMI input 1 and immediately find the signage controller. It then does a switch to HDMI 1 on the display.
|
||||
|
||||
All four of the above could be logically combined in a series of calls to define a possible “scene” in a room: Put Document camera on Projector 1, put Laptop on Projector 2 and the audio, put Signage on the Lobby display. They key takeaway is that the developer doesn’t need to define what is involved in making a certain route. The person configuring the system defines how it’s wired up, and the code only needs to tell a given destination to get a source, likely through configuration as well.
|
||||
|
||||
All of the above routes can be defined in source list routing tables, covered elsewhere (**make link)**.
|
||||
|
||||
---
|
||||
|
||||
### Definitions
|
||||
|
||||
#### Ports
|
||||
|
||||
Ports are logical representations of the input and output ports on a device.
|
||||
|
||||
#### Source
|
||||
|
||||
A source is a device at the beginning of a signal chain. For example, a set-top box, or a camera. Source devices typically have only output ports.
|
||||
|
||||
Source devices in Essentials must implement `IRoutingOutputs` or `IRoutingSource`
|
||||
|
||||
#### Midpoint
|
||||
|
||||
A midpoint is a device in the middle of the signal chain. Typically a switcher, matrix or otherwise. Examples: DM chassis; DM-TX; DM-RMC; A video codec. These devices will have input and output ports.
|
||||
|
||||
Midpoint devices must implement `IRoutingInputsOutputs`. Midpoints with switching must implement `IRouting`.
|
||||
|
||||
#### Sink
|
||||
|
||||
A sink is a device at the end of a full signal path. For example, a display, amplifier, encoder, etc. Sinks typically contain only input ports. They may or may not have switching, like a display with several inputs. Classes defining sink devices must implement `IRoutingSinkNoSwitching` or `IRoutingSinkWithSwitching`.
|
||||
|
||||
#### Tie-line
|
||||
|
||||
A tie-line is a logical representation of a physical cable connection between two devices. It has five properties that define how the tie-line connects two devices. A configuration snippet for a single tie line connecting HDMI output 1 on a Cisco RoomKit to HDMI input 1 on a display, carrying both audio and video, is shown below.
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceKey": "ciscoSparkPlusCodec-1",
|
||||
"sourcePort": "HdmiOut1",
|
||||
"destinationKey": "display-1",
|
||||
"destinationPort": "HdmiIn1",
|
||||
"type": "audioVideo"
|
||||
}
|
||||
```
|
||||
|
||||
### Interfaces
|
||||
|
||||
Todo: Define Interfaces IRouting, IRoutingOutputs, IRoutingInputs
|
||||
274
docs/docs/Debugging.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Methods of Debugging
|
||||
|
||||
1. You can use Visual Studio step debugging
|
||||
- Pros:
|
||||
- Detailed real time debugging into with breakpoints and object inspection
|
||||
- Cons:
|
||||
- Doesn't really work remotely
|
||||
- On processors with Control Subnet, you must be connected to the CS interface to use step debugging. Often not practical or possible.
|
||||
- No logging
|
||||
- Using breakpoints stops the program and can interrupt system usage
|
||||
- Requires the running application to be built in debug mode, not release mode
|
||||
2. You can use the Debug class features build into the PepperDash.Core library.
|
||||
- Pros:
|
||||
- Can be easily enabled from console
|
||||
- Allows for setting the level of verbosity
|
||||
- Works when troubleshooting remotely and doesn't require a connection to the CS interface of the processor.
|
||||
- Allows for logging to the Crestron error log or a custom log stored on removable media
|
||||
- Works regardless of build type setting (debug/release)
|
||||
- Can easily identify which class instance is generating console messages
|
||||
- Can use console commands to view the state of public properties on devices
|
||||
- Can use console commands to call methods on devices
|
||||
- Doesn't stop the program
|
||||
- Cons:
|
||||
- No detailed object inspection in real time
|
||||
- Only prints console statements already in code
|
||||
- When enabled at the highest level of verbosity, it can produce a significant amount of data in console. Can be hard to find messages easily.
|
||||
- No current mechanism to filter messages by device. (can be filtered by 3rd party tools easily, though)
|
||||
- Not very effective in debugging applications running on the VC-4 platform as only log messages get printed to the Syslog
|
||||
|
||||
## How to use the PepperDash.Core Debug Class
|
||||
|
||||
The majority of interaction is done via console, preferably via an SSH session through Crestron Toolbox, PuTTy or any other suitable application.
|
||||
|
||||
In code, the most useful method is `Debug.Console()` which has several overloads. All variations take an integer value for the level (0-2) as the first argument. Level 0 will ALWAYS print. Level 1 is for typical debug messages and level 2 is for verbose debugging. In cases where the overloads that accept a `Debug.ErrorLogLevel` parameter are used, the message will ALWAYS be logged, but will only print to console if the current debug level is the same or higher than the level set in the `Debug.Console()` statement.
|
||||
|
||||
All statements printed to console are prefixed by a timestamp which can be greatly helpful in debugging order of operations.
|
||||
|
||||
```cs
|
||||
// The most basic use, sets the level (0) and the message to print.
|
||||
Debug.Console(0, "Hello World");
|
||||
// prints: [timestamp]App 1:Hello World
|
||||
|
||||
// The string parameter has a built in string.Format() that takes params object[] items
|
||||
string world = "World";
|
||||
Debug.Console(0, "Hello {0}", world);
|
||||
// prints: [timestamp]App 1:Hello World
|
||||
|
||||
// This overload takes an IKeyed as the second parameter and the resulting statement will
|
||||
// print the Key of the device in console to help identify the class instance the message
|
||||
// originated from
|
||||
Debug.Console(0, this, "Hello World");
|
||||
// prints: [timestamp]App 1:[deviceKey]Hello World
|
||||
|
||||
// Each of the above overloads has a corresponding variant that takes an argument to indicate
|
||||
// the level of error to log the message at as well as printing to console
|
||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Hello World");
|
||||
// prints: [timestamp]App 1:Hello World
|
||||
```
|
||||
|
||||
## Console Commands
|
||||
|
||||
### General Console Commands
|
||||
|
||||
Below are is a non-exhaustive list of some of the Essentials specific console commands that allow interaction with the application at runtime.
|
||||
|
||||
### `help user`
|
||||
|
||||
Will print the available console commands for each program slot. Console commands can be added and removed dynamically by Essentials and may vary by the version of Essentials that is running. This is the best place to start to determine the available commands registered for each instance of Essentials running on a processor.
|
||||
|
||||
### `reportversions:[slot]`
|
||||
|
||||
Will print the running versions of all .dll libraries. Useful for determining the exact build version of the Essentials application and all plugins
|
||||
|
||||
### `gettypes:[slot] [searchString(optional)]`
|
||||
|
||||
The `searchString` value is an optional parameter to filter the results.
|
||||
|
||||
Will print all of the valid `type` values registered in the `DeviceFactory` for the running Essentials application. This helps when generating config structure and defining devices. Device types added by plugins will also be shown.
|
||||
|
||||
### `showconfig:[slot]`
|
||||
|
||||
Will print out the merged config object
|
||||
|
||||
### `donotloadonnextboot:[slot] [true/false]`
|
||||
|
||||
When the value is set to true, Essentials will pause when starting up, to allow for a developer to attach to the running process from an IDE for purposes of step debugging. Once attached, issuing the command `go:[slot]` will cause the configuration file to be read and the program to initialize. This value gets set to false when the `go` command is issues.
|
||||
|
||||
### DeviceManager Console Commands
|
||||
|
||||
The following console commands all perform actions on devices that have been registered with the `PepperDash.Essentials.Core.DeviceManager` static class
|
||||
|
||||
### `Appdebug:[slot][0-2]`
|
||||
|
||||
Gets or sets the current debug level where 0 is the lowest setting and 2 is the most verbose
|
||||
|
||||
### `getjoinmap:[slot] [bridgeKey][deviceKey (optional)]
|
||||
|
||||
For use with SIMPL Bridging. Prints the join map for the specified bridge. If a device key is specified, only the joins for that device will be printed.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
RMC3>appdebug:1 // Gets current level
|
||||
RMC3>AppDebug level = 0
|
||||
|
||||
RMC3>appdebug:1 1 // Sets level to 1 (all messages level 1 or lower will print)
|
||||
RMC3>[Application 1], Debug level set to 1
|
||||
```
|
||||
|
||||
### `Devlist:[slot]`
|
||||
|
||||
Gets the current list of devices from `DeviceManager`
|
||||
|
||||
Prints in the form [deviceKey] deviceName
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
// Get the list of devices for program 1
|
||||
RMC3>devlist:1
|
||||
|
||||
RMC3>[16:34:05.819]App 1:28 Devices registered with Device Mangager:
|
||||
[16:34:05.834]App 1: [cec-1] Tx 5 cec 1
|
||||
[16:34:05.835]App 1: [cec-1-cec]
|
||||
[16:34:05.835]App 1: [cec-5] Rmc 1 cec 1
|
||||
[16:34:05.836]App 1: [cec-5-cec]
|
||||
[16:34:05.836]App 1: [cec-6] Dm Chassis In 1 cec 1
|
||||
[16:34:05.837]App 1: [cec-6-cec]
|
||||
[16:34:05.837]App 1: [cec-7] Dm Chassis Out 1 cec 1
|
||||
[16:34:05.838]App 1: [cec-7-cec]
|
||||
[16:34:05.838]App 1: [comm-1] Generic comm 1
|
||||
[16:34:05.838]App 1: [comm-1-com]
|
||||
[16:34:05.839]App 1: [comm-2] Rmc comm 1
|
||||
[16:34:05.839]App 1: [comm-2-com]
|
||||
[16:34:05.840]App 1: [comm-3] Rmc comm 2
|
||||
[16:34:05.840]App 1: [comm-3-com]
|
||||
[16:34:05.841]App 1: [dmMd8x8-1] DM-MD8x8 Chassis 1
|
||||
[16:34:05.842]App 1: [dmRmc100C-1] DM-RMC-100-C Out 3
|
||||
[16:34:05.843]App 1: [dmRmc200C-1] DM-RMC-200-C Out 2
|
||||
[16:34:05.843]App 1: [dmRmc4kScalerC-1] DM-RMC-4K-SCALER-C Out 1
|
||||
[16:34:05.844]App 1: [dmTx201C-1] DM-TX-201C 1
|
||||
[16:34:05.845]App 1: [eisc-1A]
|
||||
[16:34:05.845]App 1: [gls-odt-1] GLS-ODT-CN 1
|
||||
[16:34:05.846]App 1: [gls-oir-1] GLS-OIR-CN 1
|
||||
[16:34:05.846]App 1: [processor]
|
||||
[16:34:05.847]App 1: [ssh-1] Generic SSH 1
|
||||
[16:34:05.847]App 1: [ssh-1-ssh]
|
||||
[16:34:05.848]App 1: [systemMonitor]
|
||||
[16:34:05.848]App 1: [tcp-1] Generic TCP 1
|
||||
[16:34:05.849]App 1: [tcp-1-tcp]
|
||||
```
|
||||
### `Setdevicestreamdebug:[slot][devicekey][both/rx/tx/off]`
|
||||
|
||||
Enables debug for communication on a single device
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
PRO3>setdevicestreamdebug:1 lights-1-com both
|
||||
|
||||
[13:13:57.000]App 1:[lights-1-com] Sending 4 characters of text: 'test'
|
||||
|
||||
PRO3>setdevicestreamdebug:1 lights-1-com off
|
||||
```
|
||||
|
||||
### `Devprops:[slot][devicekey]`
|
||||
|
||||
Gets the list of public properties on the device with the corresponding `deviceKey`
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
// Get the properties on the device with Key 'cec-1-cec'
|
||||
// This device happens to be a CEC port on a DM-TX-201-C's HDMI input
|
||||
RMC3>devprops:1 cec-1-cec
|
||||
[
|
||||
{
|
||||
"Name": "IsConnected",
|
||||
"Type": "Boolean",
|
||||
"Value": "True",
|
||||
"CanRead": true,
|
||||
"CanWrite": false
|
||||
},
|
||||
{
|
||||
"Name": "Key",
|
||||
"Type": "String",
|
||||
"Value": "cec-1-cec",
|
||||
"CanRead": true,
|
||||
"CanWrite": true
|
||||
},
|
||||
{
|
||||
"Name": "Name",
|
||||
"Type": "String",
|
||||
"Value": "",
|
||||
"CanRead": true,
|
||||
"CanWrite": true
|
||||
},
|
||||
{
|
||||
"Name": "Enabled",
|
||||
"Type": "Boolean",
|
||||
"Value": "False",
|
||||
"CanRead": true,
|
||||
"CanWrite": true
|
||||
}
|
||||
]
|
||||
|
||||
RMC3>
|
||||
|
||||
```
|
||||
|
||||
### `Devmethods:[slot][devicekey]`
|
||||
|
||||
Gets the list of public methods available on the device
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
// Get the methods on the device with Key 'cec-1-cec'
|
||||
RMC3>devmethods:1 cec-1-cec
|
||||
[
|
||||
{
|
||||
"Name": "SendText",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "text",
|
||||
"Type": "String"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SendBytes",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "bytes",
|
||||
"Type": "Byte[]"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SimulateReceive",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "s",
|
||||
"Type": "String"
|
||||
}
|
||||
]
|
||||
},
|
||||
//... Response abbreviated for clarity ...
|
||||
]
|
||||
|
||||
RMC3>
|
||||
```
|
||||
|
||||
### `Devjson:[slot][json formatted object {"devicekey", "methodname", "params"}]`
|
||||
|
||||
Used in conjunction with devmethods, this command allows any of the public methods to be called from console and the appropriate arguments can be passed in to the method via a JSON object.
|
||||
|
||||
This command is most useful for testing without access to hardware as it allows both simulated input and output for a device.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
// This command will call the SendText(string text) method on the
|
||||
// device with the Key 'cec-1-cec' and pass in "hello world" as the
|
||||
// argument parameter. On this particular device, it would cause
|
||||
// the string to be sent via the CEC Transmit
|
||||
RMC3>devjson:1 {"deviceKey":"cec-1-cec", "methodName":"SendText", "params": ["hello world\r"]}
|
||||
|
||||
// This command will call SimulateReceive(string text) on the device with Key 'cec-1-cec'
|
||||
// This would simulate receiving data on the CEC port of the DM-TX-201-C's HDMI input
|
||||
RMC3>devjson:1 {"deviceKey":"cec-1-cec", "methodName":"SimulateReceive", "params": ["hello citizen of Earth\r"]}
|
||||
```
|
||||
|
||||
For additional examples, see this [file](https://github.com/PepperDash/Essentials/blob/main/devjson%20commands.json).
|
||||
171
docs/docs/DigitalInput.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# DigitalInput
|
||||
|
||||
Digital Inputs can be bridged directly to SIMPL from any device that is both inlcuded within essentials and has a relay.
|
||||
|
||||
Consider the following example.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 0,
|
||||
"type": "pro3",
|
||||
"name": "pro3",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"compliance",
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"key": "DigitalInput-1",
|
||||
"uid": 3,
|
||||
"name": "Digital Input 1",
|
||||
"group": "api",
|
||||
"type": "digitalInput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 1,
|
||||
"disablePullUpResistor" : true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "DigitalInput-2",
|
||||
"uid": 3,
|
||||
"name": "Digital Input 2",
|
||||
"group": "api",
|
||||
"type": "digitalInput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 2,
|
||||
"disablePullUpResistor" : true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscapiadv",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "DigitalInput-1",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "DigitalInput-2",
|
||||
"joinStart": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## RelayOutput Configuration Explanation
|
||||
|
||||
This configuration is meant for a Pro3 device, and instantiates two relay ports and links them to an eisc bridge to another processor slot on ipid 3. Let's break down the ```DigitalInput-1``` device.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"key": "DigitalInput-1",
|
||||
"uid": 3,
|
||||
"name": "Digital Input 1",
|
||||
"group": "api",
|
||||
"type": "digitalInput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 1,
|
||||
"disablePullUpResistor" : true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**```Key```**
|
||||
|
||||
The Key is a unique identifier for essentials. The key allows the device to be linked to other devices also defined by key. All Keys MUST be unique, as every device is added to a globally-accessible dictionary. If you have accidentally utilized the same key twice, Essentials will notify you during startup that there is an issue with the device.
|
||||
|
||||
**```Uid```**
|
||||
|
||||
The Uid is reserved for use with an PepperDash internal config generation tool, and is not useful to Essentials in any way.
|
||||
|
||||
**```Name```**
|
||||
|
||||
The Name a friendly name assigned to the device. Many devices pass this data to the bridge for utilization in SIMPL.
|
||||
|
||||
**```Group```**
|
||||
|
||||
Utilized in certain Essentials devices. In this case, the value is unimportant.
|
||||
|
||||
**```Type```**
|
||||
|
||||
The Type is the identifier for a specific type of device in Essentials. A list of all valid types can be reported by using the consolecommand ```gettypes``` in Essentials. In this case, the type is ```digitalInput```. This type is valid for any instance of a Relay Output.
|
||||
|
||||
**```Properties```**
|
||||
|
||||
These are the properties essential to the instantiation of the identified type.
|
||||
|
||||
### Properties
|
||||
|
||||
There are two properties relevant to the instantiation of a relay device.
|
||||
|
||||
**```portDeviceKey```**
|
||||
|
||||
This property maps to the ```key``` of the device upon which the relay resides.
|
||||
|
||||
**```portNumber```**
|
||||
|
||||
This property maps to the number of the relay on the device you have mapped the relay device to. Even if the device has only a single relay, ```portNumber``` must be defined.
|
||||
|
||||
**```disablePullUpResistor```**
|
||||
|
||||
This is a boolean value, therefore it is a case-sensitive ```true``` or ```false``` utilized to determine if the pullup resistor on the digital input will be disabled or not.
|
||||
|
||||
### The JoinMap
|
||||
|
||||
The joinmap for a ```digitalInput``` device is comprised of a single digital join.
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class IDigitalInputJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
|
||||
[JoinName("InputState")]
|
||||
public JoinDataComplete InputState = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Room Email Url", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
|
||||
public IDigitalInputJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(IDigitalInputJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```InputState``` is a digital join that represents the feedback for the associated Digital Input Device. Its join is set to 1.
|
||||
128
docs/docs/Feedback-Classes.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Feedback classes
|
||||
|
||||
***
|
||||
* [YouTube Video - Using Feedbacks in PepperDash Essentials](https://youtu.be/5GQVRKbD9Rk)
|
||||
***
|
||||
|
||||
The various Feedback classes are like "signals". They can enable various events, and are designed to be used where we need small data events to be sent without requiring custom handlers.
|
||||
|
||||
## Why Feedbacks?
|
||||
|
||||
We have been writing "code" in an environment, Simpl, for years and have taken for granted the power that signals in that environment give us. With the release of the ability to develop in C#, we have been handed a massive set of tools to potentially make our lives better, but because of the age and limited scope of the .NET 3.5 Compact Framework, many of the things that have been very easy to do in the past have become challenging or bulky to write. Crestron classes have things called "Sigs", which are a less-functional version of the signal that we used in Simpl, but we have no ability to use our own Sigs around our own classes. This forces us to break out of the constraints and mindset of Simpl programming, but simultaneously keeps us partially bound to the "old way" of doing things.
|
||||
|
||||
Signals as we have known them since Simpl came around are great. They allow a certain type of functional programming to be built, where things operate in solutions, and we are given a whole set of behaviors that we don't really have to think about: Something goes high, the next thing responds, something else happens, etc. With our older C# framework, it is most straightforward (and least-flexible) to take Sig transitions and handle them using very-flat and bulky coding techniques: Switch/case blocks, if/else blocks, slow dictionaries... In the Essentials environment (and in many other frameworks) these methods quickly reveal their flaws.
|
||||
|
||||
Enter the Feedback. We want to define simple events that can be attached to various things - TP Sigs, EISC, event handlers - and maintain their own state. This simplifies the interface to various device classes, and allows us to define functional, simple classes with well-defined means of connecting them together.
|
||||
|
||||
### Feedbacks are similar to signals
|
||||
|
||||
Feedbacks can:
|
||||
|
||||
- Fire an event (OutputChange)
|
||||
- Be linked to one or more matching Crestron Sigs and update those Sigs
|
||||
- May contain complex computations to define the output value
|
||||
- Be put into test mode and have their value function overridden
|
||||
|
||||
A Feedback is defined on a class using a C# construct called a `Func`. A `Func` is a small operation that returns a single value and is typically written in a lambda. The operation/expression in the `Func` is calculated when FireUpdate() is called on the Feedback. The result is then available for all objects listening to this Feedback.
|
||||
|
||||
[Func documentation (MSDN)](<https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx>)
|
||||
|
||||
#### Creating Feedbacks
|
||||
|
||||
The following `IntFeedback` returns the value of the `_VolumeLevel` field in this display class:
|
||||
|
||||
```cs
|
||||
public class MyDisplay
|
||||
{
|
||||
public IntFeedback VolumeLevelFeedback { get; private set; }
|
||||
|
||||
...
|
||||
|
||||
public MyDisplay(...)
|
||||
{
|
||||
VolumeLevelFeedback = new IntFeedback(() => { return _VolumeLevel; });
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
This BoolFeedback, adapted from the DmTx201Controller class, defines the `Func` first, and then creates the BoolFeedback using that `Func`. The value returned is true if the input is the digital-HDMI connection, and the TX hardware's VideoAttributes.HdcpActiveFeedback is true as well.
|
||||
|
||||
```cs
|
||||
public class MyTx
|
||||
{
|
||||
public BoolFeedback HdcpActiveFeedback { get; private set; }
|
||||
|
||||
Func HdcpActiveFeedbackFunc = () =>
|
||||
ActualVideoInput == DmTx200Base.eSourceSelection.Digital
|
||||
&& tx.HdmiInput.VideoAttributes.HdcpActiveFeedback.BoolValue,
|
||||
|
||||
...
|
||||
|
||||
public MyTx(...)
|
||||
{
|
||||
HdcpActiveFeedback = new BoolFeedback(HdcpActiveFeedbackFunc);
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
#### Triggering Feedback
|
||||
|
||||
In your classes, when you need to update the objects listening to a Feedback, you will call MyFeedback.FireUpdate() inside your class. This will trigger the evaluation of the Func value, update any linked Sigs, and fire the OutputChange event.
|
||||
|
||||
```cs
|
||||
int _VolumeLevel;
|
||||
|
||||
void ComDataChanged(string data) // volume=77
|
||||
{
|
||||
if(data.StartsWith("volume="))
|
||||
{
|
||||
_VolumeLevel = MyParseVolumeMethod(data); // get the level, 77
|
||||
VolumeLevelFeedback.FireUpdate(); // all listeners updated
|
||||
|
||||
```
|
||||
|
||||
#### Using Feedbacks
|
||||
|
||||
Feedbacks of the various types have BoolValue, IntValue, UShortValue, and StringValue properties that return the current value of the Feedback.
|
||||
|
||||
```cs
|
||||
if (MyTxDevice.HdcpActiveFeedback.BoolValue)
|
||||
{
|
||||
... do something that needs to happen when HDCP is active ...
|
||||
```
|
||||
|
||||
Feedbacks all share an OutputChange event, that fires an event with an empty EventArgs object. The event handler can go get the appropriate \*Value property when the event fires. The example below is a bit contrived, but explains the idea.
|
||||
|
||||
```cs
|
||||
...
|
||||
MyDisplayDevice.VolumeLevelFeedback.OutputChange += MyDisplayVolumeHandler;
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
void MyDisplayVolumeHandler(object o, EventArgs a)
|
||||
{
|
||||
MobileControlServer.VolumeLevel = MyDisplayDevice.VolumeLevelFeedback.IntValue;
|
||||
```
|
||||
|
||||
Feedbacks also have a LinkInputSig(\*InputSig sig) method that can directly trigger one or more Sigs on a Crestron device, without requiring an event handler. This is very useful for attaching states of our devices to Crestron touchpanels or EISCs, for example. The BoolFeedback class also has a LinkComplementInputSig(BoolInputSig sig) method that will invert the BoolFeedback's value to one or more attached Sigs.
|
||||
|
||||
As well as updating upon change, the Feedback will set the Sig's value to the Feedback value upon calling the LinkInputSig method. This eliminates the need to walk through an object, property-by-property, and update several Sig values - as well as setting up to watch those values for changes. It is all handled in one step.
|
||||
|
||||
```cs
|
||||
public class MyClass
|
||||
{
|
||||
Tsw760 MyTp;
|
||||
|
||||
MyDisplay Display;
|
||||
|
||||
HookUpSigsMethod()
|
||||
{
|
||||
...
|
||||
|
||||
// changes to VolumeLevelFeedback will automatically propagate to UShortInputSig 123
|
||||
// changes to HdcpActiveFeedback will propagate to BoolInputSig 456
|
||||
// and these two panel Sigs are updated immediately as well.
|
||||
Display.VolumeLevelFeedback.LinkInputSig(MyTp.UshortInput[123]);
|
||||
MyHdcpDevice.HdcpActiveFeedback.LinkInputSig(MyTp.BoolInput[456]);
|
||||
```
|
||||
381
docs/docs/GenericComm.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# GenericComm
|
||||
|
||||
One of the most common scenarios in control system development is utilizing RS232 to connect to a device. Essentials doesn't restrict you to connecting a native essentials device or plugin to the comport. You can directly access the comport, and even set baudrates on the fly if you so desire.
|
||||
|
||||
Similarly you can instantiate one of several socket types in this manner and bridge them directly to SIMPL.
|
||||
|
||||
Consider the following example.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 0,
|
||||
"type": "pro3",
|
||||
"name": "pro3",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"compliance",
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"key": "Comport-1",
|
||||
"uid": 3,
|
||||
"name": "Comport 1",
|
||||
"group": "api",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "com",
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 115200,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 1,
|
||||
"controlPortDevKey": "processor",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Comport-2",
|
||||
"uid": 3,
|
||||
"name": "Comport 2",
|
||||
"group": "api",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "ssh",
|
||||
"tcpSshProperties": {
|
||||
"address": "192.168.1.57",
|
||||
"port": 22,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"autoReconnect": true,
|
||||
"autoReconnectIntervalMs": 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscapiadv",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "Comport-1",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "Comport-2",
|
||||
"joinStart": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GenericComm Configuration Explanation
|
||||
|
||||
This configuration is meant for a Pro3 device, and instantiates one comport and one SSH session and links them to an eisc bridge to another processor slot on ipid 3. Let's break down the ```Comport-1``` device.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"key": "Comport-1",
|
||||
"uid": 3,
|
||||
"name": "Comport 1",
|
||||
"group": "comm",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 115200,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 1,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**```Key```**
|
||||
|
||||
The Key is a unique identifier for essentials. The key allows the device to be linked to other devices also defined by key. All Keys MUST be unique, as every device is added to a globally-accessible dictionary. If you have accidentally utilized the same key twice, Essentials will notify you during startup that there is an issue with the device.
|
||||
|
||||
**```Uid```**
|
||||
|
||||
The Uid is reserved for use with an PepperDash internal config generation tool, and is not useful to Essentials in any way.
|
||||
|
||||
**```Name```**
|
||||
|
||||
The Name a friendly name assigned to the device. Many devices pass this data to the bridge for utilization in SIMPL.
|
||||
|
||||
**```Group```**
|
||||
|
||||
Utilized in certain Essentials devices. In this case, the value is unimportant.
|
||||
|
||||
**```Type```**
|
||||
|
||||
The Type is the identifier for a specific type of device in Essentials. A list of all valid types can be reported by using the consolecommand ```gettypes``` in Essentials. In this case, the type is ```genericComm```. This type is valid for any instance of a serial-based communications channel such as a Serial Port, SSH, UDP, or standard TCP/IP Socket.
|
||||
|
||||
**```Properties```**
|
||||
|
||||
These are the properties essential to the instantiation of the identified type.
|
||||
|
||||
#### Control
|
||||
|
||||
The properties within this property are dependant on the type of genericComm you wish to instantiate. There is one common property for control of any type, and that is ```method```. The ```method``` property requires a string that maps to the following enumerations in Essentials :
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
// Summary:
|
||||
// Crestron Control Methods for a comm object
|
||||
public enum eControlMethod
|
||||
{
|
||||
None = 0,
|
||||
Com = 1,
|
||||
IpId = 2,
|
||||
IpidTcp = 3,
|
||||
IR = 4,
|
||||
Ssh = 5,
|
||||
Tcpip = 6,
|
||||
Telnet = 7,
|
||||
Cresnet = 8,
|
||||
Cec = 9,
|
||||
Udp = 10,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These enumerations are not case sensitive. Not all methods are valid for a ```genericComm``` device. For a comport, the only valid type would be ```Com```. For a direct network socket, valid options are ```Ssh```, ```Tcpip```, ```Telnet```, and ```Udp```.
|
||||
|
||||
##### ComParams
|
||||
|
||||
A ```Com``` device requires a ```comParams``` object to set the properties of the comport. The values of all properties are case-insensitive.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 115200,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Valid ```hardwareHandshake``` values are as follows**
|
||||
|
||||
```sh
|
||||
"None"
|
||||
"Rts"
|
||||
"Cts"
|
||||
"RtsCts"
|
||||
```
|
||||
|
||||
**Valid ```parity``` values are as follows**
|
||||
|
||||
```sh
|
||||
"None"
|
||||
"Even"
|
||||
"Odd"
|
||||
"Mark"
|
||||
```
|
||||
|
||||
**Valid ```protocol``` values are as follows**
|
||||
|
||||
```sh
|
||||
"RS232"
|
||||
"RS422"
|
||||
"RS485"
|
||||
```
|
||||
|
||||
**Valid ```baudRate``` values are as follows**
|
||||
|
||||
```sh
|
||||
300
|
||||
600
|
||||
1200
|
||||
1800
|
||||
2400
|
||||
3600
|
||||
4800
|
||||
7200
|
||||
9600
|
||||
14400
|
||||
19200
|
||||
28800
|
||||
38400
|
||||
57600
|
||||
115200
|
||||
```
|
||||
|
||||
**Valid ```dataBits``` values are as follows**
|
||||
|
||||
```sh
|
||||
7
|
||||
8
|
||||
```
|
||||
|
||||
**Valid ```softwareHandshake``` values are as follows**
|
||||
|
||||
```sh
|
||||
"None"
|
||||
"XON"
|
||||
"XONT"
|
||||
"XONR"
|
||||
```
|
||||
|
||||
**Valid ```stopBits``` values are as follows**
|
||||
|
||||
```sh
|
||||
1
|
||||
2
|
||||
```
|
||||
|
||||
Additionally, a ```control``` object for a physical hardware port needs to map to that physical port. This is accomplished by utilizing the ```controlPortDevKey``` and ```port``` properties.
|
||||
|
||||
**```controlPortDevKey```**
|
||||
|
||||
This property maps to the ```key``` of the device upon which the port resides.
|
||||
|
||||
**```port```**
|
||||
|
||||
This property maps to the number of the port on the device you have mapped the relay device to. Even if the device has only a single port, ```port``` must be defined.
|
||||
|
||||
##### TcpSshParams
|
||||
|
||||
A ```Ssh```, ```TcpIp```, or ```Udp``` device requires a ```tcpSshProperties``` object to set the propeties of the socket.
|
||||
|
||||
```Json
|
||||
{
|
||||
"tcpSshProperties": {
|
||||
"address": "192.168.1.57",
|
||||
"port": 22,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"autoReconnect": true,
|
||||
"autoReconnectIntervalMs": 10000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**```address```**
|
||||
|
||||
This is the IP address, hostname, or FQDN of the resource you wish to open a socket to. In the case of a UDP device, you can set either a single whitelist address with this data, or an appropriate broadcast address.
|
||||
|
||||
**```port```**
|
||||
|
||||
This is the port you wish to utilize for the socket connection. Certain protocols require certain ports - ```Ssh``` being ```22``` and ```Telnet``` being ```23```.
|
||||
|
||||
**```username```**
|
||||
|
||||
This is the username (if required) for authentication to the device you are connecting to. Typcally only required for ```Ssh``` connections.
|
||||
|
||||
**```password```**
|
||||
|
||||
This is the password (if required) for authentication to the device you are connecting to. Typcally only required for ```Ssh``` connections.
|
||||
|
||||
**```autoreconnect```**
|
||||
|
||||
This is a boolean value, therefore it is a case-sensitive ```true``` or ```false``` utilized to determine if the socket will attempt to reconnect upon loss of connection.
|
||||
|
||||
**```autoReconnectIntervalMs```**
|
||||
|
||||
This is the duration of time, in Miliseconds, that the socket will wait before discrete connection attempts if ```autoreconnect``` is set to true.
|
||||
|
||||
##### The JoinMap
|
||||
|
||||
The join map for a generic comms device is fairly simple.
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class IBasicCommunicationJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
[JoinName("TextReceived")]
|
||||
public JoinDataComplete TextReceived = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Received From Remote Device", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SendText")]
|
||||
public JoinDataComplete SendText = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Sent To Remote Device", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SetPortConfig")]
|
||||
public JoinDataComplete SetPortConfig = new JoinDataComplete(new JoinData() { JoinNumber = 2, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Set Port Config", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("Connect")]
|
||||
public JoinDataComplete Connect = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connect", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Connected")]
|
||||
public JoinDataComplete Connected = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Status")]
|
||||
public JoinDataComplete Status = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog });
|
||||
|
||||
|
||||
public IBasicCommunicationJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(IBasicCommunicationJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```TextReceived``` is a stream of strings received **FROM** the connected device.
|
||||
|
||||
```SendText``` is for any strings you wish to send **TO** the connected device.
|
||||
|
||||
```Connect``` connects to a remote socket device on the rising edge of the signal.
|
||||
|
||||
```Connected``` represents the current connection state. High for Connected, low for Disconnected.
|
||||
|
||||
```Status``` is an analog value that is representative of the connection states as reported by the SIMPL TCP/IP socket symbol.
|
||||
|
||||
All of the preceeding joins are set to join ```1```. The second serial input join is reserved for ```2```. It allows you to send a ```comparams``` json object as a string, utilizing the same format mentioned previously in this document. Doing so will override the configured comport specifications.
|
||||
26
docs/docs/Get-started.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Get started
|
||||
|
||||
---
|
||||
[YouTube Video - Getting Started with PepperDash Essentials](https://youtu.be/FxEZtbpCwiQ)
|
||||
***
|
||||
|
||||
## Download or clone
|
||||
|
||||
You may clone Essentials at <https://github.com/PepperDash/Essentials.git>
|
||||
|
||||
## How to Get Started
|
||||
|
||||
This section assumes knowledge of loading programs to and working with the file system on a Crestron processor.
|
||||
|
||||
1. Using an SFTP client, load `PepperDashEssentials1.4.32.cpz` to the processor in program slot 1 and start the program by sending console command `progload -p:1`
|
||||
1. On first boot, the Essentials Application will build the necessary configuration folder structure in the User/Program1/ path.
|
||||
1. The application has some example configuration files included. Copy `/Program01/Example Configuration/EssentialsSpaceHuddleRoom/configurationFile-HuddleSpace-2-Source.json` to the `/User/Program1/` folder.
|
||||
1. Copy the SGD files from `/Program01/SGD` to `/User/Program1/sgd`
|
||||
1. Reset the program via console `progreset -p:1`. The program will load the example configuration file.
|
||||
1. Via console, you can run the `devlist:1` command to get some insight into what has been loaded from the configuration file into the system . This will print the basic device information in the form of ["key"] "Name". The "key" value is what we can use to interact with each device uniquely.
|
||||
1. Run the command `devprops:1 display-1`. This will print the real-time property values of the device with key "display-1".
|
||||
1. Run the command `devmethods:1 display-1`. This will print the public methods available for the device with key "display-1".
|
||||
1. Run the command `devjson:1 {"deviceKey":"display-1","methodName":"PowerOn", "params": []}`. This will call the method PowerOn() on the device with key "display-1".
|
||||
1. Run the provided example XPanel SmartGraphics project and connect to your processor at the appropriate IPID.
|
||||
|
||||
Next: [Standalone use](~/docs/Standalone-Use.md)
|
||||
25
docs/docs/Glossary-of-Terms.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Glossary of Terms
|
||||
|
||||
**Assembly**
|
||||
An assembly is a file that is automatically generated by the compiler upon successful compilation of every . NET application. It can be either a Dynamic Link Library or an executable file. It is generated only once for an application and upon each subsequent compilation the assembly gets updated.
|
||||
|
||||
**Device**
|
||||
A base class, defined in the PepperDash.Core library (`PepperDash.Core.Device`). It can represent a physical device, or a virtual device or behaviour. Generally, most new classes defined in the Essentials ecosystem should derive from Device.
|
||||
|
||||
**DeviceManager**
|
||||
A static class (`PepperDash.Core.Essentials.DeviceManager`) that contains an unordered collection of Devices. Devices are added/registered to the DeviceManager and later can be retrieved as references by Key.
|
||||
|
||||
**Essentials Application**
|
||||
A Crestron SIMPL# Pro application that is made up of the Essentials Framework and any optionally any number of Essentials Plugins
|
||||
|
||||
**Essentials Framework**
|
||||
The collection of core libraries that make up the framework
|
||||
|
||||
**Essentials Plugins**
|
||||
SIMPL# Pro libraries that reference the Essentials Framework and are loaded at runtime to add or extend functionality
|
||||
|
||||
**IKeyed**
|
||||
An important interface defined in PepperDash.Core that requires a string property named Key, whose value must be unique.
|
||||
|
||||
**PepperDash.Core**
|
||||
A SIMPL# utility library referenced by Essentials Framework.
|
||||
59
docs/docs/Home.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Welcome to PepperDash Essentials!
|
||||
|
||||
PepperDash Essentials is an open-source framework for control systems, built on Crestron's Simpl# Pro framework. It can be configured as a standalone program capable of running a wide variety of system designs and can also be used to augment other Crestron programs.
|
||||
|
||||
Essentials is a collection of C# libraries that can be used in many ways. It is a 100% configuration-driven framework that can be extended to add different workflows and behaviors, either through the addition of new device-types and classes, or via a plug-in mechanism. The framework is a collection of things that are all related and interconnected, but in general do not have strong dependencies on each other.
|
||||
|
||||
---
|
||||
|
||||
## Get started
|
||||
|
||||
- [Download essentials build or clone repo](~/docs/Get-started.md)
|
||||
- [How to get started](~/docs/Get-started.md)
|
||||
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
|
||||
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
|
||||
|
||||
Or use the links to the right to navigate our documentation.
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
- Runs on Crestron 3-Series, **4-Series** and VC-4 Control System platforms
|
||||
- Reduced hardware overhead compared to S+ and Simpl solutions
|
||||
- Quick development cycle
|
||||
- Shared resources made easily available
|
||||
- More flexibility with less code
|
||||
- Configurable using simple JSON files
|
||||
- Is awesome
|
||||
|
||||
---
|
||||
|
||||
## Comment
|
||||
|
||||
The Essentials wiki is clearly in-progress right now. Take a look at the links to the right. We are actively working on this documentation, so please be patient with us. If you have any comments on or suggestions for the documentation, please file an issue here, with as much detail as you can provide: <https://github.com/PepperDash/Essentials/issues>
|
||||
|
||||
Thanks!
|
||||
|
||||
---
|
||||
|
||||
## Collaboration
|
||||
|
||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
|
||||
|
||||
### Open-source-collaborative workflow
|
||||
|
||||
The `main` branch always contain the latest stable version. The `development` branch is used for most development efforts.
|
||||
|
||||
[GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) will be used as the workflow for this collaborative project. To contribute, follow this process:
|
||||
|
||||
1. Fork this repository ([More Info](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks))
|
||||
2. Create a branch using standard GitFlow branch prefixes (feature/hotfix) followed by a descriptive name.
|
||||
- Example: `feature/add-awesomeness` or `hotfix/really-big-oops`
|
||||
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
|
||||
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
|
||||
4. When the scope of the work for your branch is complete, make sure to rebase your branch in case further progress has been made since the repo was forked
|
||||
5. Create a Pull Request to pull your branch into the appropriate branch in the main repository.
|
||||
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.
|
||||
|
||||
Next: [Get started](~/docs/Get-started.md)
|
||||
103
docs/docs/IR-Driver-Bridging.md
Normal file
@@ -0,0 +1,103 @@
|
||||
## Legacy IR Driver Bridging
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Apple TV",
|
||||
"key": "appleTv-1",
|
||||
"type": "genericIrController",
|
||||
"uid": 3,
|
||||
"group": "devices",
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "ir",
|
||||
"irFile": "Apple_AppleTV_4th_Gen_Essentials.ir",
|
||||
"controlPortDevKey": "processor",
|
||||
"controlPortNumber": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Bridge Join Map IR Driver Bridging
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Apple TV",
|
||||
"key": "appleTv-1",
|
||||
"type": "genericIrController",
|
||||
"uid": 3,
|
||||
"group": "devices",
|
||||
"properties": {
|
||||
"control": {
|
||||
"method": "ir",
|
||||
"irFile": "Apple_AppleTV_4th_Gen_Essentials.ir",
|
||||
"controlPortDevKey": "processor",
|
||||
"controlPortNumber": "1",
|
||||
"useBridgeJoinMap": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Both methods will bridge the IR signals with `Standard Command` defined in the IR file.
|
||||
|
||||
The `useBridgeJoinMap` property implements `GenericIrControllerJoinMap.cs` to standardized IR driver `Standard Command` signal joins. This allows users to swap IR drivers that implement `Standard Command` while bridging IR signals consistently between drivers. For example, when `useBridgeJoinMap` is present, channel up will be mapped to join-22 + device `joinstart` for any IR driver that has the signal marked as `Standard Command`.
|
||||
|
||||
|
||||
## GenericIrControllerJoinMap (Example)
|
||||
|
||||
### Digitals
|
||||
|
||||
| Join Number | Join Span | Description | Type | Capabilities |
|
||||
| ----------- | --------- | ----------- | ------------------- | ------------ |
|
||||
| 1 | 1 | PLAY | Digital | FromSIMPL |
|
||||
| 2 | 1 | STOP | Digital | FromSIMPL |
|
||||
| 3 | 1 | PAUSE | Digital | FromSIMPL |
|
||||
| 4 | 1 | FSCAN | Digital | FromSIMPL |
|
||||
| 5 | 1 | RSCAN | Digital | FromSIMPL |
|
||||
| 9 | 1 | POWER | Digital | FromSIMPL |
|
||||
| 10 | 1 | 0 | Digital | FromSIMPL |
|
||||
| 11 | 1 | 1 | Digital | FromSIMPL |
|
||||
| 12 | 1 | 2 | Digital | FromSIMPL |
|
||||
| 13 | 1 | 3 | Digital | FromSIMPL |
|
||||
| 14 | 1 | 4 | Digital | FromSIMPL |
|
||||
| 15 | 1 | 5 | Digital | FromSIMPL |
|
||||
| 16 | 1 | 6 | Digital | FromSIMPL |
|
||||
| 17 | 1 | 7 | Digital | FromSIMPL |
|
||||
| 18 | 1 | 8 | Digital | FromSIMPL |
|
||||
| 19 | 1 | 9 | Digital | FromSIMPL |
|
||||
| 21 | 1 | ENTER | Digital | FromSIMPL |
|
||||
| 22 | 1 | CH+ | Digital | FromSIMPL |
|
||||
| 23 | 1 | CH- | Digital | FromSIMPL |
|
||||
| 27 | 1 | POWER_ON | Digital | FromSIMPL |
|
||||
| 28 | 1 | POWER_OFF | Digital | FromSIMPL |
|
||||
| 30 | 1 | LAST | Digital | FromSIMPL |
|
||||
| 41 | 1 | BACK | Digital | FromSIMPL |
|
||||
| 42 | 1 | GUIDE | Digital | FromSIMPL |
|
||||
| 43 | 1 | INFO | Digital | FromSIMPL |
|
||||
| 44 | 1 | MENU | Digital | FromSIMPL |
|
||||
| 45 | 1 | UP_ARROW | Digital | FromSIMPL |
|
||||
| 46 | 1 | DN_ARROW | Digital | FromSIMPL |
|
||||
| 47 | 1 | LEFT_ARROW | Digital | FromSIMPL |
|
||||
| 48 | 1 | RIGHT_ARROW | Digital | FromSIMPL |
|
||||
| 49 | 1 | SELECT | Digital | FromSIMPL |
|
||||
| 54 | 1 | PAGE_UP | Digital | FromSIMPL |
|
||||
| 55 | 1 | PAGE_DOWN | Digital | FromSIMPL |
|
||||
| 61 | 1 | A | Digital | FromSIMPL |
|
||||
| 62 | 1 | B | Digital | FromSIMPL |
|
||||
| 63 | 1 | C | Digital | FromSIMPL |
|
||||
| 64 | 1 | D | Digital | FromSIMPL |
|
||||
|
||||
### Analogs
|
||||
|
||||
| Join Number | Join Span | Description | Type | Capabilities |
|
||||
| ----------- | --------- | ----------- | ------------------- | ------------ |
|
||||
|
||||
### Serials
|
||||
|
||||
| Join Number | Join Span | Description | Type | Capabilities |
|
||||
| ----------- | --------- | ----------- | ------------------- | ------------ |
|
||||
|
||||
|
||||
155
docs/docs/JoinMaps.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# What is a Join Map?
|
||||
|
||||
A join map is a class that defines the list of joins accessible to an `EssentialsBridgeableDevice` across an EISC Bridge.
|
||||
|
||||
## Why use a Join Map?
|
||||
|
||||
A join map is necessary to bridge joins across an EISC bridge from Essentials to a SIMPL program. Join maps can be overriden in a configuration as necessary, but by default each device has a standard join map that publishes joins as a function of the joinOffset property of the device within a given Essentials Bridge. Join maps are reusable and extensible. Several join maps for standard device types already exist within Essentials, and those can be utilized for plugin creation without creation of a new join map. Utilizing standard join maps allows you to create a consistent API between device types that allows switching of devices via config without any new SIMPL or SIMPL#Pro code being written.
|
||||
|
||||
## How Join maps Work
|
||||
|
||||
Whenever you instantiate a device and link that device to an EISC bridge utilizing your configuration in Essentials, the method `LinkToApi()` is called. This method matches various methods, feedbacks, and properties to joins on the EISC bridge in order to create a consistent API for communication to SIMPL.
|
||||
|
||||
Whenever `LinkToApi()` is called, it creates a new instance of the device's joinMap class on demand. The constructor of that joinMap creates an object containing the join metadata, adds any configured join offsets to the standard join map, and adds all associated joins to a global join list that can be easily referenced from the command line.
|
||||
|
||||
There are several components for each join within a join map.
|
||||
|
||||
```cs
|
||||
[JoinName("Online")]
|
||||
public JoinDataComplete Online = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Reports Online Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
```
|
||||
|
||||
### Attribute
|
||||
|
||||
```cs
|
||||
[JoinName("Online")]
|
||||
```
|
||||
|
||||
If the attribute is present, the join data is added to the publically available list `Joins`. This can be used to "prebuild" functionality within a join map that you may not yet need. If you do not add this attribute (or simply comment it out), the join data will not be displayed whenever join data is printed using the `getjoinmap` command.
|
||||
|
||||
### JoinData
|
||||
|
||||
```cs
|
||||
JoinData() { JoinNumber = 1, JoinSpan = 1 };
|
||||
```
|
||||
|
||||
`JoinData` contains the pertinent information for the bridge. JoinData contains the information that the bridge utilizes to create each associated connection from the EISC to the methods, properties, and feedbacks associated with a device.
|
||||
|
||||
`JoinNumber` is the 1-based index of the join you wish to tie to a given method, property, or feedback. This join, combined with the offset defined in the brdige's configuration for a device, will give you the SIMPL EISC join linked to the given data.
|
||||
|
||||
`JoinSpan` determines a number of associated joins. Perhaps you have a list of Camera Presets, or a list of inputs. You can create one single join map entry and define the span of all associated join types.
|
||||
|
||||
A `JoinData` object with a `JoinNumber` of 11 and a `JoinSpan` of 10 and a `joinOffset` of 0 is associated with joins 11-20 on the EISC.
|
||||
|
||||
### JoinMetadata
|
||||
|
||||
```cs
|
||||
JoinMetadata() { Label = "Reports Online Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital }
|
||||
```
|
||||
|
||||
`JoinMetadata` provides the data reported when the `getjoinmap` command is used.
|
||||
|
||||
`Label` is the description of the what this join does.
|
||||
|
||||
`JoinCapabilities` is represented by an enum defining the direction that the data is flowing for this join. Appropriate values are:
|
||||
|
||||
```cs
|
||||
public enum eJoinCapabilities
|
||||
{
|
||||
None = 0,
|
||||
ToSIMPL = 1,
|
||||
FromSIMPL = 2,
|
||||
ToFromSIMPL = ToSIMPL | FromSIMPL
|
||||
}
|
||||
```
|
||||
|
||||
`JoinType` is represented by an enum defining the data type in SIMPL. Appropriate values are:
|
||||
|
||||
```cs
|
||||
public enum eJoinType
|
||||
{
|
||||
None = 0,
|
||||
Digital = 1,
|
||||
Analog = 2,
|
||||
Serial = 4,
|
||||
DigitalAnalog = Digital | Analog,
|
||||
DigitalSerial = Digital | Serial,
|
||||
AnalogSerial = Analog | Serial,
|
||||
DigitalAnalogSerial = Digital | Analog | Serial
|
||||
}
|
||||
```
|
||||
|
||||
### JoinDataComplete
|
||||
|
||||
```chsarp
|
||||
JoinDataComplete(JoinData data, JoinMetadata metadata);
|
||||
```
|
||||
|
||||
`JoinDataComplete` represents the `JoinData` and the `JoinMetadata` in a single object. You can call an instance of `JoinDataComplete` to report any information about a specific join. In a device bridge, you would utilize the `JoinNumber` property to link a feature from the plugin to the EISC API.
|
||||
|
||||
### Example Join Map
|
||||
|
||||
This is the join map for `IBasicCommunication` Devices
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class IBasicCommunicationJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
[JoinName("TextReceived")]
|
||||
public JoinDataComplete TextReceived = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Received From Remote Device", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SendText")]
|
||||
public JoinDataComplete SendText = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Sent To Remote Device", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SetPortConfig")]
|
||||
public JoinDataComplete SetPortConfig = new JoinDataComplete(new JoinData() { JoinNumber = 2, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Set Port Config", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("Connect")]
|
||||
public JoinDataComplete Connect = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connect", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Connected")]
|
||||
public JoinDataComplete Connected = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Status")]
|
||||
public JoinDataComplete Status = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog });
|
||||
|
||||
|
||||
public IBasicCommunicationJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(IBasicCommunicationJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Returning Data on Join Maps
|
||||
|
||||
A mechanism for printing join maps to console from a running Essentials program is built in to Essentials.
|
||||
|
||||
Given a single Generic Communication device with a `joinStart` of 11 and a key of "Com-1", defined on a bridge with a key of "Bridge-1" and IPID of A0, the command `getjoinmap Bridge-1 Com-1` would return:
|
||||
|
||||
```sh
|
||||
Join Map for device 'Com-1' on EISC 'Bridge-1':
|
||||
GenericComm:
|
||||
|
||||
Digitals:
|
||||
Found 2 Digital Joins
|
||||
Join Number: '11' | JoinSpan: '1' | Label : 'Connect' | Type: 'Digital' | Capabilities : 'FromSimpl'
|
||||
Join Number: '11' | JoinSpan: '1' | Label : 'Connected' | Type: 'Digital' | Capabilities : 'ToSimpl'
|
||||
Analogs:
|
||||
Found 1 Analog Joins
|
||||
Join Number: '11' | JoinSpan: '1' | Label : 'Status' | Type: 'Analog' | Capabilities : 'ToSimpl'
|
||||
Serials:
|
||||
Found 3 Serial Joins
|
||||
Join Number: '11' | JoinSpan: '1' | Label : 'Text Received From Remote Device' | Type: 'Serial' | Capabilities : 'FromSimpl'
|
||||
Join Number: '11' | JoinSpan: '1' | Label : 'Text Sent To Remote Device' | Type: 'Serial' | Capabilities : 'ToSimpl'
|
||||
Join Number: '12' | JoinSpan: '1' | Label : 'Set Port Config' | Type: 'Serial' | Capabilities : 'FromSimpl'
|
||||
```
|
||||
74
docs/docs/Plugins-Deprecated.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Deprecated
|
||||
|
||||
**Note : this entry is out of date - please see [Plugins](~/docs/Plugins.md)**
|
||||
|
||||
## What are Essentials Plugins?
|
||||
|
||||
Plugins are SIMPL# Pro libraries that reference the Essentials Framework and can be loaded into an Essentials Application at runtime to extend functionality beyond what the Essentials Framework provides on its own.
|
||||
|
||||
## Why Use Plugins?
|
||||
|
||||
Plugins are a way to extend or add new functionality to the Essentials Application without having to modify the actual Framework. In most cases, a plugin can be written to support a new device or behavior. Using plugins also limits the scope of understanding needed to work within the Essentials Framework.
|
||||
|
||||
## Should I use a Plugin?
|
||||
|
||||
Essentials is meant to be a lightweight framework and an extensible basis for development. While some devices are included in the framework, mostly for the purposes of providing examples and developing and prototyping new device types, the bulk of new development is intended to take place in Plugins. Once a plugin adds new functionality that may be of benefit if shared across multiple plugins, it may make sense to port that common logic (base classes and/or interfaces) back into the framework to make it available to others. The thrust of future Essentials development is targeted towards building a library of plugins.
|
||||
|
||||
## How do Plugins Work?
|
||||
|
||||
One or more plugins can be loaded to the /user/ProgramX/plugins as .dlls or .cplz packages. When the Essentials Application starts, it looks for any .cplz files, unzips them and then iterates any .dll assemblies in that folder and loads them. Once the plugin assemblies are loaded the Essentials Application will then attempt to load a configuration file and construct items as defined in the file. Those items can be defined in either the Essentials Framework or in any of the loaded plugin assemblies.
|
||||
|
||||

|
||||
|
||||
## What Must be Implemented in a Plugin for it to Work?
|
||||
|
||||
All plugin assemblies must contain a static method called LoadPlugin():
|
||||
|
||||
```cs
|
||||
public class SomeDevice : Device , IBridge //IBridge only needs to be implemented if using a bridge
|
||||
{
|
||||
// This string is used to define the minimum version of the
|
||||
// Essentials Framework required for this plugin
|
||||
public static string MinimumEssentialsFrameworkVersion = "1.4.23";
|
||||
|
||||
// This method gets called by the Essentials Framework when the application starts.
|
||||
// It is intended to be used to load the new Device type(s) specified in the plugin
|
||||
public static void LoadPlugin()
|
||||
{
|
||||
DeviceFactory.AddFactoryForType("typeName", FactoryMethod);
|
||||
// typeName should be the unique string that identifies the type of device to build,
|
||||
// FactoryMethod represents the method that takes a DevicConfig object as and argument
|
||||
// and returns an instance of the desired device type
|
||||
}
|
||||
|
||||
// This is a factory method to construct a device and return it to be
|
||||
// added to the DeviceManager
|
||||
public static Device FactoryMethod(DeviceConfig dc)
|
||||
{
|
||||
return new SomeDevice(dc.key, dc.name, dc);
|
||||
}
|
||||
|
||||
#region IBridge
|
||||
// This method is called by an EiscApi bridge instance and should call an extension method
|
||||
// defined in your plugin. Required for implementing IBridge
|
||||
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey)
|
||||
{
|
||||
this.LinkToApiExt(trilist, joinStart, joinMapKey);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
```
|
||||
|
||||
## SIMPL Bridging
|
||||
|
||||
Optionally, if your plugin device needs to be able to bridge to a SIMPL program over EISC, and there isn't already an existing bridge class in the Essentials Framework, you can write a new bridge class in your plugin. However, in order for the Essentials Application to be able to us that new bridge, the bridge must implement the IBridge interface with the required LinkToApi() Extension method.
|
||||
|
||||
Often though, you may find that a bridge class already exists in the Essentials Framework that you can leverage. For example, if you were writing a plugin to support a new display model that isn't already in the Essentials Framework, you would define a class in your plugin that inherits from PepperDash.Essentials.Core.DisplayBase. If you're only implementing the standard display control functions such as power/input/volume control, then the existing bridge class `DisplayControllerBridge` can be used. If you needed to add additional functions to the bridge, then you would need to write your own bridge in the plugin.
|
||||
|
||||
For additional info see the [SIMPL-Bridging article](~/docs/SIMPL-Bridging.md).
|
||||
|
||||
## Template Essentials Plugin Repository
|
||||
|
||||
Fork this repository when starting a new plugin. The template repository uses the essentials-builds repository as a submodule. This allows the plugin to reference a specific build version of Essentials. You must make sure that you checkout the correct build of the Essentials-Builds repo that contains any dependencies that your plugin may rely on.
|
||||
|
||||
[Essentials Plugin Template Repository](https://github.com/PepperDash/EssentialsPluginTemplate)
|
||||
131
docs/docs/Plugins.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# What are Essentials Plugins?
|
||||
|
||||
**Note : this entry updates a deprecated method - for information related to that deprecated method, see [Plugins - Deprecated](~/docs/Plugins-Deprecated.md)**
|
||||
|
||||
***
|
||||
* [YouTube Video - Loading Plugins in PepperDash Essentials](https://youtu.be/NA64iyNNAgE)
|
||||
* [YouTube Video - Build Your Own Plugin, Part 1](https://youtu.be/m2phC8g3Kfk)
|
||||
* [YouTube Video - Build Your Own Plugin, Part 2](https://youtu.be/2_PrWRk6Gy0)
|
||||
***
|
||||
|
||||
Plugins are SIMPL# Pro libraries that reference the Essentials Framework and can be loaded into an Essentials Application at runtime to extend functionality beyond what the Essentials Framework provides on its own.
|
||||
|
||||
## Why Use Plugins?
|
||||
|
||||
Plugins are a way to extend or add new functionality to the Essentials Application without having to modify the actual Framework. In most cases, a plugin can be written to support a new device or behavior. Using plugins also limits the scope of understanding needed to work within the Essentials Framework.
|
||||
|
||||
## Should I use a Plugin?
|
||||
|
||||
Essentials is meant to be a lightweight framework and an extensible basis for development. While some devices are included in the framework, mostly for the purposes of providing examples and developing and prototyping new device types, the bulk of new development is intended to take place in Plugins. Once a plugin adds new functionality that may be of benefit if shared across multiple plugins, it may make sense to port that common logic (base classes and/or interfaces) back into the framework to make it available to others. The thrust of future Essentials development is targeted towards building a library of plugins.
|
||||
|
||||
## How do Plugins Work?
|
||||
|
||||
One or more plugins can be loaded to the /user/ProgramX/plugins as .dlls or .cplz packages. When the Essentials Application starts, it looks for any .cplz files, unzips them and then iterates any .dll assemblies in that folder and loads them. Once the plugin assemblies are loaded the Essentials Application will then attempt to load a configuration file and construct items as defined in the file. Those items can be defined in either the Essentials Framework or in any of the loaded plugin assemblies.
|
||||
|
||||

|
||||
|
||||
## What Must be Implemented in a Plugin for it to Work?
|
||||
|
||||
All plugin assemblies must contain a class which inherits from ```EssentialsPluginDeviceFactory<T>```, where ```<T>``` is a class which inherits from ```PepperDash.Essentials.Core.EssentialsDevice```
|
||||
|
||||
Within this class, we will define some metadata for the plugin and define which constructor to use
|
||||
for instantiating each class as defined by type.
|
||||
|
||||
Note that multiple types can be loaded from the same plugin.
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyPlugin
|
||||
{
|
||||
public class SomeDeviceFactory : EssentialsPluginDeviceFactory<SomeDevice>
|
||||
{
|
||||
// This method defines metadata for the devices in the plugin
|
||||
public SomeDeviceFactory()
|
||||
{
|
||||
// This string is used to define the minimum version of the
|
||||
// Essentials Framework required for this plugin
|
||||
MinimumEssentialsFrameworkVersion = "1.5.0";
|
||||
|
||||
// This string is used to define all valid type names for
|
||||
// this plugin. This string is case insensitive
|
||||
TypeNames = new List<string> { "SomeDevice" , "ThisNewDevice" };
|
||||
}
|
||||
|
||||
// This method instantiates new devices for the defined type
|
||||
// within the plugin
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
// Deserialize the json properties for the loaded type to a new object
|
||||
var props = dc.Properties.ToObject<SomeDeviceProperties>();
|
||||
|
||||
// Return a newly instantiated device using the desired constructor
|
||||
// If no valid property data exists, return null with console print
|
||||
// describing the failure.
|
||||
if (props == null)
|
||||
{
|
||||
Debug.Console(0, "No valid property data for 'SomeDevice' - Verify Configuration.");
|
||||
Debug.Console(0, dc.Properties.ToString());
|
||||
return null;
|
||||
}
|
||||
return new SomeDevice(dc.Key, dc.Name, props);
|
||||
}
|
||||
}
|
||||
|
||||
public class OtherDeviceFactory : EssentialsPluginDeviceFactory<OtherDevice>
|
||||
{
|
||||
// This method defines metadata for the devices in the plugin
|
||||
public OtherDeviceFactory()
|
||||
{
|
||||
// This string is used to define the minimum version of the
|
||||
// Essentials Framework required for this plugin
|
||||
MinimumEssentialsFrameworkVersion = "1.5.0";
|
||||
|
||||
// This string is used to define all valid type names for
|
||||
// this plugin. This string is case insensitive
|
||||
TypeNames = new List<string> { "OtherDevice", "ThisOtherDevice" };
|
||||
}
|
||||
|
||||
// This method instantiates new devices for the defined type
|
||||
// within the plugin
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
// Deserialize the json properties for the loaded type to a new object
|
||||
var props = dc.Properties.ToObject<OtherDeviceProperties>();
|
||||
|
||||
// Return a newly instantiated device using the desired constructor
|
||||
// If no valid property data exists, return null with console print
|
||||
// describing the failure.
|
||||
if (props == null)
|
||||
{
|
||||
Debug.Console(0, "No valid property data for 'OtherDevice' - Verify Configuration.");
|
||||
Debug.Console(0, dc.Properties.ToString());
|
||||
return null;
|
||||
}
|
||||
return new OtherDevice(dc.Key, dc.Name, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SIMPL Bridging
|
||||
|
||||
Optionally, if your plugin device needs to be able to bridge to a SIMPL program over EISC, and there isn't already an existing bridge class in the Essentials Framework, you can write a new bridge class in your plugin. However, in order for the Essentials Application to be able to us that new bridge, the bridge must implement the ```IBridgeAdvanced``` interface with the required ```LinkToApi()``` Extension method.
|
||||
|
||||
If you are writing code for a bridgeable device, you should be inheriting from ```EssentialsBridgeableDevice```, which inherits from the already-required ```EssentialsDevice``` and implements ```IBridgeAdvanced```.
|
||||
|
||||
Often though, you may find that a bridge class already exists in the Essentials Framework that you can leverage. For example, if you were writing a plugin to support a new display model that isn't already in the Essentials Framework, you would define a class in your plugin that inherits from PepperDash.Essentials.Core.DisplayBase. If you're only implementing the standard display control functions such as power/input/volume control, then the existing bridge class `DisplayControllerBridge` can be used. If you needed to add additional functions to the bridge, then you would need to write your own bridge in the plugin.
|
||||
|
||||
For additional info see the [SIMPL-Bridging article](~/docs/SIMPL-Bridging.md).
|
||||
|
||||
## Template Essentials Plugin Repository
|
||||
|
||||
Fork this repository when starting a new plugin. The template repository uses the essentials-builds repository as a submodule. This allows the plugin to reference a specific build version of Essentials. You must make sure that you checkout the correct build of the Essentials-Builds repo that contains any dependencies that your plugin may rely on.
|
||||
|
||||
[Essentials Plugin Template Repository](https://github.com/PepperDash/EssentialsPluginTemplate)
|
||||
164
docs/docs/RelayOutput.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# RelayOutput
|
||||
|
||||
Relays can be bridged directly to SIMPL from any device that is both inlcuded within essentials and has a relay.
|
||||
|
||||
Consider the following example.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 0,
|
||||
"type": "pro3",
|
||||
"name": "pro3",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"compliance",
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"key": "Relay-1",
|
||||
"uid": 3,
|
||||
"name": "Relay 1",
|
||||
"group": "api",
|
||||
"type": "relayOutput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Relay-2",
|
||||
"uid": 3,
|
||||
"name": "Relay 2",
|
||||
"group": "api",
|
||||
"type": "relayOutput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscapiadv",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "Relay-1",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "Relay-2",
|
||||
"joinStart": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## RelayOutput Configuration Explanation
|
||||
|
||||
This configuration is meant for a Pro3 device, and instantiates two relay ports and links them to an eisc bridge to another processor slot on ipid 3. Let's break down the ```Relay-1``` device.
|
||||
|
||||
```JSON
|
||||
{
|
||||
"key": "Relay-1",
|
||||
"uid": 3,
|
||||
"name": "Relay 1",
|
||||
"group": "api",
|
||||
"type": "relayOutput",
|
||||
"properties": {
|
||||
"portDeviceKey" : "processor",
|
||||
"portNumber" : 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**```Key```**
|
||||
|
||||
The Key is a unique identifier for essentials. The key allows the device to be linked to other devices also defined by key. All Keys MUST be unique, as every device is added to a globally-accessible dictionary. If you have accidentally utilized the same key twice, Essentials will notify you during startup that there is an issue with the device.
|
||||
|
||||
**```Uid```**
|
||||
|
||||
The Uid is reserved for use with an PepperDash internal config generation tool, and is not useful to Essentials in any way.
|
||||
|
||||
**```Name```**
|
||||
|
||||
The Name a friendly name assigned to the device. Many devices pass this data to the bridge for utilization in SIMPL.
|
||||
|
||||
**```Group```**
|
||||
|
||||
Utilized in certain Essentials devices. In this case, the value is unimportant.
|
||||
|
||||
**```Type```**
|
||||
|
||||
The Type is the identifier for a specific type of device in Essentials. A list of all valid types can be reported by using the consolecommand ```gettypes``` in Essentials. In this case, the type is ```relayOutput```. This type is valid for any instance of a Relay Output.
|
||||
|
||||
**```Properties```**
|
||||
|
||||
These are the properties essential to the instantiation of the identified type.
|
||||
|
||||
### Properties
|
||||
|
||||
There are two properties relevant to the instantiation of a relay device.
|
||||
|
||||
**```portDeviceKey```**
|
||||
|
||||
This property maps to the ```key``` of the device upon which the relay resides.
|
||||
|
||||
**```portNumber```**
|
||||
|
||||
This property maps to the number of the relay on the device you have mapped the relay device to. Even if the device has only a single relay, ```portNumber``` must be defined.
|
||||
|
||||
### The JoinMap
|
||||
|
||||
The joinmap for a ```relayOutput``` device is comprised of a single digital join.
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class GenericRelayControllerJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
|
||||
[JoinName("Relay")]
|
||||
public JoinDataComplete Relay = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Device Relay State Set / Get", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
|
||||
public GenericRelayControllerJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(GenericRelayControllerJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```Relay``` is a digital join that represents both the trigger and the feedback for the associated relay device. Its join is set to 1.
|
||||
477
docs/docs/SIMPL-Bridging-Deprecated.md
Normal file
@@ -0,0 +1,477 @@
|
||||
# Deprecated
|
||||
|
||||
**Note : this entry is out of date - please see [SIMPL Windows Bridging](~/docs/SIMPL-Bridging.md)**
|
||||
|
||||
## SIMPL Windows Bridging - Deprecated
|
||||
|
||||
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
|
||||
|
||||
Some of the main advantages are:
|
||||
|
||||
1. The ability to instantiate devices from configuration.
|
||||
1. The ability to leverage C# concepts to handle data intensive tasks (Serialization/Deserialization of JSON/XML, cyrptography, etc.).
|
||||
1. The ability to reuse the same compiled SIMPL Windows program (regardless of target processor type) by offloading all the variables that may be room or hardware specific to Essentials.
|
||||
1. The ability to handle multiple communciation types generically without changing the SIMPL Program (TCP/UDP/SSH/HTTP/HTTPS/CEC, etc.)
|
||||
1. Much faster development cycle
|
||||
1. Reduced processor overhead
|
||||
1. Ability to easily share devices defined in Essentials between multiple other programs
|
||||
|
||||
## Implementation
|
||||
|
||||
Bridges are devices that are defined within the devices array in the config file. They are unique devices with a specialized purpose; to act as a bridge between Essentials Devices and applications programmed traditionally in Simpl Windows. This is accomplished by instantiating a Three Series Intersystem Communication symbol within the bridge device, and linking its Boolean/Ushort/String inputs and outputs to actions on one or multiple Essentials device(s). The definition for which joins map to which actions is defined within the device to be bridged to in a class that derives from JoinMapBase.
|
||||
|
||||
Let's consider the following Essentials Configuration:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 1,
|
||||
"type": "pro3",
|
||||
"name": "PRO3 w/o cards",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {
|
||||
"numberOfComPorts": 6,
|
||||
"numberOfIrPorts": 8,
|
||||
"numberOfRelays": 8,
|
||||
"numberOfDIOPorts": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "panasonicDisplay01",
|
||||
"type": "PanasonicThefDisplay",
|
||||
"name": "Main Display",
|
||||
"group": "displays",
|
||||
"uid": 2,
|
||||
"properties": {
|
||||
"id": "01",
|
||||
"inputNumber": 1,
|
||||
"outputNumber": 1,
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 9600,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 1,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "vtcComPort",
|
||||
"uid": 3,
|
||||
"name": "VTC Coms",
|
||||
"group": "comm",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 38400,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 2,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscApi",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "panasonicDisplay01",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "vtcComPort",
|
||||
"joinStart": 51
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have four Essentials Devices configured:
|
||||
|
||||
1. Pro3 with a Key of "processor"
|
||||
|
||||
1. Panasonic Display with a Key of "panasonicDisplay01"
|
||||
|
||||
1. Com port with a Key of "vtcComPort"
|
||||
|
||||
1. Bridge with a Key of "deviceBridge"
|
||||
|
||||
We want to have access to the com port for VTC Control from Simpl Windows and we want to control the display from Simpl Windows. To accomplish this, we have created a bridge device and added the devices to be bridged to the "devices" array on the bridge. As you can see we define the device key and the join start, which will determine which joins we will use on the resulting EISC to interact with the devices. In the Bridge control properties we defined ipid 03, and we will need a corresponding Ethernet System Intercommunication in the Simpl Windows program at ipid 03.
|
||||
|
||||
Now that our devices have been built, we can refer to the device join maps to see which joins correspond to which actions.
|
||||
|
||||
See below:
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Bridges
|
||||
{
|
||||
public class DisplayControllerJoinMap : JoinMapBase
|
||||
{
|
||||
#region Digitals
|
||||
/// <summary>
|
||||
/// Turns the display off and reports power off feedback
|
||||
/// </summary>
|
||||
public uint PowerOff { get; set; }
|
||||
/// <summary>
|
||||
/// Turns the display on and repots power on feedback
|
||||
/// </summary>
|
||||
public uint PowerOn { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates that the display device supports two way communication when high
|
||||
/// </summary>
|
||||
public uint IsTwoWayDisplay { get; set; }
|
||||
/// <summary>
|
||||
/// Increments the volume while high
|
||||
/// </summary>
|
||||
public uint VolumeUp { get; set; }
|
||||
/// <summary>
|
||||
/// Decrements teh volume while high
|
||||
/// </summary>
|
||||
public uint VolumeDown { get; set; }
|
||||
/// <summary>
|
||||
/// Toggles the mute state. Feedback is high when volume is muted
|
||||
/// </summary>
|
||||
public uint VolumeMute { get; set; }
|
||||
/// <summary>
|
||||
/// Range of digital joins to select inputs and report current input as feedback
|
||||
/// </summary>
|
||||
public uint InputSelectOffset { get; set; }
|
||||
/// <summary>
|
||||
/// Range of digital joins to report visibility for input buttons
|
||||
/// </summary>
|
||||
public uint ButtonVisibilityOffset { get; set; }
|
||||
/// <summary>
|
||||
/// High if the device is online
|
||||
/// </summary>
|
||||
public uint IsOnline { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Analogs
|
||||
/// <summary>
|
||||
/// Analog join to set the input and report current input as feedback
|
||||
/// </summary>
|
||||
public uint InputSelect { get; set; }
|
||||
/// <summary>
|
||||
/// Sets the volume level and reports the current level as feedback
|
||||
/// </summary>
|
||||
public uint VolumeLevel { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Serials
|
||||
/// <summary>
|
||||
/// Reports the name of the display as defined in config as feedback
|
||||
/// </summary>
|
||||
public uint Name { get; set; }
|
||||
/// <summary>
|
||||
/// Range of serial joins that reports the names of the inputs as feedback
|
||||
/// </summary>
|
||||
public uint InputNamesOffset { get; set; }
|
||||
#endregion
|
||||
|
||||
public DisplayControllerJoinMap()
|
||||
{
|
||||
// Digital
|
||||
IsOnline = 50;
|
||||
PowerOff = 1;
|
||||
PowerOn = 2;
|
||||
IsTwoWayDisplay = 3;
|
||||
VolumeUp = 5;
|
||||
VolumeDown = 6;
|
||||
VolumeMute = 7;
|
||||
|
||||
ButtonVisibilityOffset = 40;
|
||||
InputSelectOffset = 10;
|
||||
|
||||
// Analog
|
||||
InputSelect = 11;
|
||||
VolumeLevel = 5;
|
||||
|
||||
// Serial
|
||||
Name = 1;
|
||||
InputNamesOffset = 10;
|
||||
}
|
||||
|
||||
public override void OffsetJoinNumbers(uint joinStart)
|
||||
{
|
||||
var joinOffset = joinStart - 1;
|
||||
|
||||
IsOnline = IsOnline + joinOffset;
|
||||
PowerOff = PowerOff + joinOffset;
|
||||
PowerOn = PowerOn + joinOffset;
|
||||
IsTwoWayDisplay = IsTwoWayDisplay + joinOffset;
|
||||
ButtonVisibilityOffset = ButtonVisibilityOffset + joinOffset;
|
||||
Name = Name + joinOffset;
|
||||
InputNamesOffset = InputNamesOffset + joinOffset;
|
||||
InputSelectOffset = InputSelectOffset + joinOffset;
|
||||
|
||||
InputSelect = InputSelect + joinOffset;
|
||||
|
||||
VolumeUp = VolumeUp + joinOffset;
|
||||
VolumeDown = VolumeDown + joinOffset;
|
||||
VolumeMute = VolumeMute + joinOffset;
|
||||
VolumeLevel = VolumeLevel + joinOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We know that the Panasonic Display uses the DisplayControllerJoinMap class and can see the join numbers that will give us access to functionality in the Device.
|
||||
|
||||
IsOnline = 50
|
||||
PowerOff = 1
|
||||
PowerOn = 2
|
||||
IsTwoWayDisplay = 3
|
||||
VolumeUp = 5
|
||||
VolumeDown = 6
|
||||
VolumeMute = 7
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Bridges
|
||||
{
|
||||
public class IBasicCommunicationJoinMap : JoinMapBase
|
||||
{
|
||||
#region Digitals
|
||||
/// <summary>
|
||||
/// Set High to connect, Low to disconnect
|
||||
/// </summary>
|
||||
public uint Connect { get; set; }
|
||||
/// <summary>
|
||||
/// Reports Connected State (High = Connected)
|
||||
/// </summary>
|
||||
public uint Connected { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Analogs
|
||||
/// <summary>
|
||||
/// Reports the connections status value
|
||||
/// </summary>
|
||||
public uint Status { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Serials
|
||||
/// <summary>
|
||||
/// Data back from port
|
||||
/// </summary>
|
||||
public uint TextReceived { get; set; }
|
||||
/// <summary>
|
||||
/// Sends data to the port
|
||||
/// </summary>
|
||||
public uint SendText { get; set; }
|
||||
/// <summary>
|
||||
/// Takes a JSON serialized string that sets a COM port's parameters
|
||||
/// </summary>
|
||||
public uint SetPortConfig { get; set; }
|
||||
#endregion
|
||||
|
||||
public IBasicCommunicationJoinMap()
|
||||
{
|
||||
TextReceived = 1;
|
||||
SendText = 1;
|
||||
SetPortConfig = 2;
|
||||
Connect = 1;
|
||||
Connected = 1;
|
||||
Status = 1;
|
||||
}
|
||||
|
||||
public override void OffsetJoinNumbers(uint joinStart)
|
||||
{
|
||||
var joinOffset = joinStart - 1;
|
||||
|
||||
TextReceived = TextReceived + joinOffset;
|
||||
SendText = SendText + joinOffset;
|
||||
SetPortConfig = SetPortConfig + joinOffset;
|
||||
Connect = Connect + joinOffset;
|
||||
Connected = Connected + joinOffset;
|
||||
Status = Status + joinOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
TextReceived = 1
|
||||
SendText = 1
|
||||
SetPortConfig = 2
|
||||
Connect = 1
|
||||
Connected = 1
|
||||
Status = 1
|
||||
|
||||
Considering our Bridge config, we can see that the display controls will start at join 1, and the VTC Com port will start at join 51. The result is a single EISC that allows us to interact with our Essentials devices.
|
||||
|
||||
To control diplay power from Simpl Windows, we would connect Digital Signals to joins 1 & 2 on the EISC to control Display Power On & Off.
|
||||
To utilize the com port device, we would connect Serial Signals (VTC_TX$ and VTC_RX$) to join 51 on the EISC.
|
||||
|
||||
You can refer to our [Simpl Windows Bridging Example](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for a more complex example.
|
||||
Example device config: <https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json>
|
||||
|
||||
## Notes
|
||||
|
||||
1. It is important to realize that there are no safety checks (yet) when assigning joinStarts in bridge configurations. If you were to put two devices on a bridge with overlapping joins, the most recently bridged join would overwrite previously bridged joins. For now it is on the programmer to ensure there are no conflicting join maps.
|
||||
|
||||
1. There is _no_ limit to the amount of times a device may be bridged to. You may have the same device on multiple bridges across multiple applications without problem. That being said, we recommend using common sense. Accessing a single com port for VTC control via multiple bridges may not be wise...
|
||||
|
||||
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||
|
||||
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||
|
||||
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
||||
|
||||
1. Related to item 5, you can use the same paradigm with respect to physical device communication. If you were to have a DSP device in some rooms communicating over RS232 and some via SSH, it would be trival to swap the device from a Com port to an SSH client in the Essentials Devicee Config and update the Bridge Config to brigde to the desired communication method. Again this would require no change on the Simpl Windows side as long as you maintain the same join Start in the Bridge Device Configuration.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. There are 10 conference rooms that all operate the same, but have hardware differences that are impossible to account for in SIMPL Windows. For example, each room might have a DM-MD8X8 chassis, but the input and output cards aren't all in the same order, or they might be different models but function the same. You can use Essentials with a unique configuration file for each hardware configuration.
|
||||
|
||||
1. You have a floor of conference rooms that all share some centralized hardware like DSP, AV Routing and a shared CEN-GWEXER gateway with multiple GLS-OIR-CSM-EX-BATT occupancy sensors. All the shared hardware can be defined in the Essentials configuration and bridged over an EISC to each program that needs access. The same device can even be exposed to multiple programs over different EISCs.
|
||||
|
||||
1. You have a SIMPL program that works for many room types, but because some rooms have different models of processors than others (CP3/CP3N/AV3/PRO3/DMPS3 variants), you have to maintain several versions of the program, compiled for each processor model to maintain access to features like the System Monitor slot. You can use Essentials running in a slot on a processor to expose the System Monitor and many other features of the processor, regardless of model. Now you only need to maintain a single SIMPL program defined for your most complex processor application (ex. PRO3)
|
||||
|
||||
## Device Type Join Maps
|
||||
|
||||
### AirMediaController
|
||||
|
||||
> supports: AM-200, AM-300
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/AirMediaControllerJoinMap.cs>
|
||||
|
||||
### AppleTvController
|
||||
|
||||
> supports: IR control of Apple TV
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/AppleTvJoinMap.cs>
|
||||
|
||||
### CameraControlBase
|
||||
|
||||
> supports: any camera that derives from CameraBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/CameraControllerJoinMap.cs>
|
||||
|
||||
### DisplayController
|
||||
|
||||
> supports: IR controlled displays, any two way display driver that derives from PepperDash.Essentials.Core.DisplayBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DisplayControllerJoinMap.cs>
|
||||
|
||||
### DmChasisController
|
||||
|
||||
> supports: All DM-MD-8x8/16x16/32x32 chassis, with or w/o DM-CPU3 Card
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmChassisControllerJoinMap.cs>
|
||||
|
||||
### DmRmcController
|
||||
|
||||
> supports: All DM-RMC devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmRmcControllerJoinMap.cs>
|
||||
|
||||
### DmTxController
|
||||
|
||||
> supports: All Dm-Tx devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmTxControllerJoinMap.cs>
|
||||
|
||||
### DmpsAudioOutputController
|
||||
|
||||
> supports: Program, Aux1, Aux2 outputs of all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs>
|
||||
|
||||
### DmpsRoutingController
|
||||
|
||||
> supports: Av routing for all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs>
|
||||
|
||||
### GenericRelayController
|
||||
|
||||
> supports: Any relay port on a Crestron Control System or Dm Endpoint
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs>
|
||||
|
||||
### GenericLightingJoinMap
|
||||
|
||||
> supports: Devices derived from PepperDash.Essentials.Core.Lighting.LightingBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GenericLightingJoinMap.cs>
|
||||
|
||||
### GlsOccupancySensorBase
|
||||
|
||||
> supports: Any Crestron GLS-Type Occupancy sensor - single/dual type
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs>
|
||||
|
||||
### HdMdxxxCEController
|
||||
|
||||
> supports: HD-MD-400-C-E, HD-MD-300-C-E, HD-MD-200-C-E, HD-MD-200-C-1G-E-B/W
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs>
|
||||
|
||||
### IBasicCommunication
|
||||
|
||||
> supports: Any COM Port on a Control System or Dm Endpoint device, TCP Client, SSH Client, or UDP Server
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs>
|
||||
|
||||
### IDigitalInput
|
||||
|
||||
> supports: Any Digital Input on a Control System, or DM Endpoint device
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/IDigitalInputJoinMap.cs>
|
||||
|
||||
### SystemMonitorController
|
||||
|
||||
> supports: Exposing the system monitor slot for any Control System
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/SystemMonitorJoinMap.cs>
|
||||
|
||||
## Example SIMPL Windows Program
|
||||
|
||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
411
docs/docs/SIMPL-Bridging-Updated.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# Use with SIMPL Windows
|
||||
|
||||
***
|
||||
* [YouTube Video - SIMPL Windows in PepperDash Essentials](https://youtu.be/P2jNzsfpgJE)
|
||||
***
|
||||
|
||||
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
|
||||
|
||||
Some of the main advantages are:
|
||||
|
||||
1. The ability to instantiate devices from configuration.
|
||||
2. The ability to leverage C# concepts to handle data intensive tasks (Serialization/Deserialization of JSON/XML, cyrptography, etc.).
|
||||
3. The ability to reuse the same compiled SIMPL Windows program (regardless of target processor type) by offloading all the variables that may be room or hardware specific to Essentials.
|
||||
4. The ability to handle multiple communciation types generically without changing the SIMPL Program (TCP/UDP/SSH/HTTP/HTTPS/CEC, etc.)
|
||||
5. Much faster development cycle
|
||||
6. Reduced processor overhead
|
||||
7. Ability to easily share devices defined in Essentials between multiple other programs
|
||||
|
||||
## Implementation
|
||||
|
||||
Bridges are devices that are defined within the devices array in the config file. They are unique devices with a specialized purpose: to act as a bridge between Essentials Devices and applications programmed traditionally in SIMPL Windows. This is accomplished by instantiating a Three Series Intersystem Communication symbol within the bridge device, and linking its Boolean/Ushort/String inputs and outputs to actions on one or multiple Essentials device(s). The definition for which joins map to which actions is defined within the device to be bridged to in a class that derives from JoinMapBase.
|
||||
|
||||
Let's consider the following Essentials Configuration:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 1,
|
||||
"type": "pro3",
|
||||
"name": "PRO3 w/o cards",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {
|
||||
"numberOfComPorts": 6,
|
||||
"numberOfIrPorts": 8,
|
||||
"numberOfRelays": 8,
|
||||
"numberOfDIOPorts": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "panasonicDisplay01",
|
||||
"type": "PanasonicThefDisplay",
|
||||
"name": "Main Display",
|
||||
"group": "displays",
|
||||
"uid": 2,
|
||||
"properties": {
|
||||
"id": "01",
|
||||
"inputNumber": 1,
|
||||
"outputNumber": 1,
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 9600,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 1,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "vtcComPort",
|
||||
"uid": 3,
|
||||
"name": "VTC Coms",
|
||||
"group": "comm",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 38400,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 2,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscApi",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "panasonicDisplay01",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "vtcComPort",
|
||||
"joinStart": 51
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have four Essentials Devices configured:
|
||||
|
||||
1. Pro3 with a Key of "processor"
|
||||
|
||||
1. Panasonic Display with a Key of "panasonicDisplay01"
|
||||
|
||||
1. Com port with a Key of "vtcComPort"
|
||||
|
||||
1. Bridge with a Key of "deviceBridge"
|
||||
|
||||
We want to have access to the com port for VTC Control from SIMPL Windows and we want to control the display from SIMPL Windows. To accomplish this, we have created a bridge device and added the devices to be bridged to the "devices" array on the bridge. As you can see we define the device key and the join start, which will determine which joins we will use on the resulting EISC to interact with the devices. In the Bridge control properties we defined ipid 03, and we will need a corresponding Ethernet System Intercommunication in the SIMPL Windows program at ipid 03.
|
||||
|
||||
Now that our devices have been built, we can refer to the device join maps to see which joins correspond to which actions.
|
||||
|
||||
See below:
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class DisplayControllerJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
[JoinName("Name")]
|
||||
public JoinDataComplete Name = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Name", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("PowerOff")]
|
||||
public JoinDataComplete PowerOff = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Power Off", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("PowerOn")]
|
||||
public JoinDataComplete PowerOn = new JoinDataComplete(new JoinData() { JoinNumber = 2, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Power On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("IsTwoWayDisplay")]
|
||||
public JoinDataComplete IsTwoWayDisplay = new JoinDataComplete(new JoinData() { JoinNumber = 3, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Is Two Way Display", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("VolumeUp")]
|
||||
public JoinDataComplete VolumeUp = new JoinDataComplete(new JoinData() { JoinNumber = 5, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Up", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("VolumeLevel")]
|
||||
public JoinDataComplete VolumeLevel = new JoinDataComplete(new JoinData() { JoinNumber = 5, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Level", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog });
|
||||
|
||||
[JoinName("VolumeDown")]
|
||||
public JoinDataComplete VolumeDown = new JoinDataComplete(new JoinData() { JoinNumber = 6, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Down", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("VolumeMute")]
|
||||
public JoinDataComplete VolumeMute = new JoinDataComplete(new JoinData() { JoinNumber = 7, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Mute", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("VolumeMuteOn")]
|
||||
public JoinDataComplete VolumeMuteOn = new JoinDataComplete(new JoinData() { JoinNumber = 8, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Mute On", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("VolumeMuteOff")]
|
||||
public JoinDataComplete VolumeMuteOff = new JoinDataComplete(new JoinData() { JoinNumber = 9, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Volume Mute Off", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("InputSelectOffset")]
|
||||
public JoinDataComplete InputSelectOffset = new JoinDataComplete(new JoinData() { JoinNumber = 11, JoinSpan = 10 },
|
||||
new JoinMetadata() { Label = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("InputNamesOffset")]
|
||||
public JoinDataComplete InputNamesOffset = new JoinDataComplete(new JoinData() { JoinNumber = 11, JoinSpan = 10 },
|
||||
new JoinMetadata() { Label = "Input Names Offset", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("InputSelect")]
|
||||
public JoinDataComplete InputSelect = new JoinDataComplete(new JoinData() { JoinNumber = 11, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Input Select", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.Analog });
|
||||
|
||||
[JoinName("ButtonVisibilityOffset")]
|
||||
public JoinDataComplete ButtonVisibilityOffset = new JoinDataComplete(new JoinData() { JoinNumber = 41, JoinSpan = 10 },
|
||||
new JoinMetadata() { Label = "Button Visibility Offset", JoinCapabilities = eJoinCapabilities.ToFromSIMPL, JoinType = eJoinType.DigitalSerial });
|
||||
|
||||
[JoinName("IsOnline")]
|
||||
public JoinDataComplete IsOnline = new JoinDataComplete(new JoinData() { JoinNumber = 50, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Is Online", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
public DisplayControllerJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(CameraControllerJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We know that the Panasonic Display uses the DisplayControllerJoinMap class and can see the join numbers that will give us access to functionality in the Device.
|
||||
|
||||
IsOnline = 50
|
||||
PowerOff = 1
|
||||
PowerOn = 2
|
||||
IsTwoWayDisplay = 3
|
||||
VolumeUp = 5
|
||||
VolumeDown = 6
|
||||
VolumeMute = 7
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
public class IBasicCommunicationJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
[JoinName("TextReceived")]
|
||||
public JoinDataComplete TextReceived = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Received From Remote Device", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SendText")]
|
||||
public JoinDataComplete SendText = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Text Sent To Remote Device", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("SetPortConfig")]
|
||||
public JoinDataComplete SetPortConfig = new JoinDataComplete(new JoinData() { JoinNumber = 2, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Set Port Config", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
[JoinName("Connect")]
|
||||
public JoinDataComplete Connect = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connect", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Connected")]
|
||||
public JoinDataComplete Connected = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Connected", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
[JoinName("Status")]
|
||||
public JoinDataComplete Status = new JoinDataComplete(new JoinData() { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata() { Label = "Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Analog });
|
||||
|
||||
|
||||
public IBasicCommunicationJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(IBasicCommunicationJoinMap))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Considering our Bridge config, we can see that the display controls will start at join 1, and the VTC Com port will start at join 51. The result is a single EISC that allows us to interact with our Essentials devices.
|
||||
|
||||
To control diplay power from SIMPL Windows, we would connect Digital Signals to joins 1 & 2 on the EISC to control Display Power On & Off.
|
||||
To utilize the com port device, we would connect Serial Signals (VTC_TX$ and VTC_RX$) to join 51 on the EISC.
|
||||
|
||||
You can refer to our [SIMPL Windows Bridging Example](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for a more complex example.
|
||||
Example device config: <https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json>
|
||||
|
||||
## Notes
|
||||
|
||||
1. It is important to realize that there are no safety checks (yet) when assigning joinStarts in bridge configurations. If you were to put two devices on a bridge with overlapping joins, the most recently bridged join would overwrite previously bridged joins. For now it is on the programmer to ensure there are no conflicting join maps.
|
||||
|
||||
2. There is _no_ limit to the amount of times a device may be bridged to. You may have the same device on multiple bridges across multiple applications without problem. That being said, we recommend using common sense. Accessing a single com port for VTC control via multiple bridges may not be wise...
|
||||
|
||||
3. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||
|
||||
4. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||
|
||||
5. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing `DisplayControllerJoinMap`. This way, you can swap plugins without needing any change on the SIMPL Windows side. This is extremely powerful when maintaining SIMPL Windows code bases for large deployments that may utilize differing equipment per room. If you can build a SIMPL Windows program that interacts with established join maps, you can swap out the device via config without any change needed to SIMPL Windows.
|
||||
|
||||
6. Related to item 5, you can use the same paradigm with respect to physical device communication. If you were to have a DSP device in some rooms communicating over RS232 and some via SSH, it would be trival to swap the device from a Com port to an SSH client in the Essentials Devicee Config and update the Bridge Config to brigde to the desired communication method. Again this would require no change on the SIMPL Windows side as long as you maintain the same join Start in the Bridge Device Configuration.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. There are 10 conference rooms that all operate the same, but have hardware differences that are impossible to account for in SIMPL Windows. For example, each room might have a DM-MD8X8 chassis, but the input and output cards aren't all in the same order, or they might be different models but function the same. You can use Essentials with a unique configuration file for each hardware configuration.
|
||||
|
||||
2. You have a floor of conference rooms that all share some centralized hardware like DSP, AV Routing and a shared CEN-GWEXER gateway with multiple GLS-OIR-CSM-EX-BATT occupancy sensors. All the shared hardware can be defined in the Essentials configuration and bridged over an EISC to each program that needs access. The same device can even be exposed to multiple programs over different EISCs.
|
||||
|
||||
3. You have a SIMPL program that works for many room types, but because some rooms have different models of processors than others (CP3/CP3N/AV3/PRO3/DMPS3 variants), you have to maintain several versions of the program, compiled for each processor model to maintain access to features like the System Monitor slot. You can use Essentials running in a slot on a processor to expose the System Monitor and many other features of the processor, regardless of model. Now you only need to maintain a single SIMPL program defined for your most complex processor application (ex. PRO3)
|
||||
|
||||
## Join Map Documentation
|
||||
|
||||
[Join Map Documentation](~/docs/JoinMaps.md)
|
||||
|
||||
## Device Type Join Maps
|
||||
|
||||
Please note that these joinmaps _may_ be using a deprecated implementation. The implementation is valid but nonetheless frowned upon for new features and plugins.
|
||||
|
||||
### AirMediaController
|
||||
|
||||
> supports: AM-200, AM-300
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/AirMediaControllerJoinMap.cs>
|
||||
|
||||
### AppleTvController
|
||||
|
||||
> supports: IR control of Apple TV
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/AppleTvJoinMap.cs>
|
||||
|
||||
### CameraControlBase
|
||||
|
||||
> supports: any camera that derives from CameraBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/CameraControllerJoinMap.cs>
|
||||
|
||||
### DisplayController
|
||||
|
||||
> supports: IR controlled displays, any two way display driver that derives from PepperDash.Essentials.Core.DisplayBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DisplayControllerJoinMap.cs>
|
||||
|
||||
### DmChasisController
|
||||
|
||||
> supports: All DM-MD-8x8/16x16/32x32 chassis, with or w/o DM-CPU3 Card
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmChassisControllerJoinMap.cs>
|
||||
|
||||
### DmRmcController
|
||||
|
||||
> supports: All DM-RMC devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmRmcControllerJoinMap.cs>
|
||||
|
||||
### DmTxController
|
||||
|
||||
> supports: All Dm-Tx devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmTxControllerJoinMap.cs>
|
||||
|
||||
### DmpsAudioOutputController
|
||||
|
||||
> supports: Program, Aux1, Aux2 outputs of all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs>
|
||||
|
||||
### DmpsRoutingController
|
||||
|
||||
> supports: Av routing for all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs>
|
||||
|
||||
### GenericRelayController
|
||||
|
||||
> supports: Any relay port on a Crestron Control System or Dm Endpoint
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs>
|
||||
|
||||
### GenericLightingJoinMap
|
||||
|
||||
> supports: Devices derived from PepperDash.Essentials.Core.Lighting.LightingBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/GenericLightingJoinMap.cs>
|
||||
|
||||
### GlsOccupancySensorBase
|
||||
|
||||
> supports: Any Crestron GLS-Type Occupancy sensor - single/dual type
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs>
|
||||
|
||||
### HdMdxxxCEController
|
||||
|
||||
> supports: HD-MD-400-C-E, HD-MD-300-C-E, HD-MD-200-C-E, HD-MD-200-C-1G-E-B/W
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs>
|
||||
|
||||
### IBasicCommunication
|
||||
|
||||
> supports: Any COM Port on a Control System or Dm Endpoint device, TCP Client, SSH Client, or UDP Server
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs>
|
||||
|
||||
### IDigitalInput
|
||||
|
||||
> supports: Any Digital Input on a Control System, or DM Endpoint device
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/IDigitalInputJoinMap.cs>
|
||||
|
||||
### SystemMonitorController
|
||||
|
||||
> supports: Exposing the system monitor slot for any Control System
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/essentials-framework/Essentials%20Core/PepperDashEssentialsBase/Bridges/JoinMaps/SystemMonitorJoinMap.cs>
|
||||
|
||||
## Example SIMPL Windows Program
|
||||
|
||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
475
docs/docs/SIMPL-Bridging.md
Normal file
@@ -0,0 +1,475 @@
|
||||
# SIMPL Windows Bridging
|
||||
|
||||
**Note : this entry is out of date - please see [SIMPL Windows Bridging - Updated](~/docs/SIMPL-Bridging-Updated.md)**
|
||||
|
||||
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
|
||||
|
||||
Some of the main advantages are:
|
||||
|
||||
1. The ability to instantiate devices from configuration.
|
||||
1. The ability to leverage C# concepts to handle data intensive tasks (Serialization/Deserialization of JSON/XML, cyrptography, etc.).
|
||||
1. The ability to reuse the same compiled SIMPL Windows program (regardless of target processor type) by offloading all the variables that may be room or hardware specific to Essentials.
|
||||
1. The ability to handle multiple communciation types generically without changing the SIMPL Program (TCP/UDP/SSH/HTTP/HTTPS/CEC, etc.)
|
||||
1. Much faster development cycle
|
||||
1. Reduced processor overhead
|
||||
1. Ability to easily share devices defined in Essentials between multiple other programs
|
||||
|
||||
## Implementation
|
||||
|
||||
Bridges are devices that are defined within the devices array in the config file. They are unique devices with a specialized purpose; to act as a bridge between Essentials Devices and applications programmed traditionally in Simpl Windows. This is accomplished by instantiating a Three Series Intersystem Communication symbol within the bridge device, and linking its Boolean/Ushort/String inputs and outputs to actions on one or multiple Essentials device(s). The definition for which joins map to which actions is defined within the device to be bridged to in a class that derives from JoinMapBase.
|
||||
|
||||
Let's consider the following Essentials Configuration:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"template": {
|
||||
"roomInfo": [
|
||||
{}
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"key": "processor",
|
||||
"uid": 1,
|
||||
"type": "pro3",
|
||||
"name": "PRO3 w/o cards",
|
||||
"group": "processor",
|
||||
"supportedConfigModes": [
|
||||
"essentials"
|
||||
],
|
||||
"supportedSystemTypes": [
|
||||
"hudType",
|
||||
"presType",
|
||||
"vtcType",
|
||||
"custom"
|
||||
],
|
||||
"supportsCompliance": true,
|
||||
"properties": {
|
||||
"numberOfComPorts": 6,
|
||||
"numberOfIrPorts": 8,
|
||||
"numberOfRelays": 8,
|
||||
"numberOfDIOPorts": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "panasonicDisplay01",
|
||||
"type": "PanasonicThefDisplay",
|
||||
"name": "Main Display",
|
||||
"group": "displays",
|
||||
"uid": 2,
|
||||
"properties": {
|
||||
"id": "01",
|
||||
"inputNumber": 1,
|
||||
"outputNumber": 1,
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 9600,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 1,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "vtcComPort",
|
||||
"uid": 3,
|
||||
"name": "VTC Coms",
|
||||
"group": "comm",
|
||||
"type": "genericComm",
|
||||
"properties": {
|
||||
"control": {
|
||||
"comParams": {
|
||||
"hardwareHandshake": "None",
|
||||
"parity": "None",
|
||||
"protocol": "RS232",
|
||||
"baudRate": 38400,
|
||||
"dataBits": 8,
|
||||
"softwareHandshake": "None",
|
||||
"stopBits": 1
|
||||
},
|
||||
"controlPortNumber": 2,
|
||||
"controlPortDevKey": "processor",
|
||||
"method": "com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "deviceBridge",
|
||||
"uid": 4,
|
||||
"name": "BridgeToDevices",
|
||||
"group": "api",
|
||||
"type": "eiscApi",
|
||||
"properties": {
|
||||
"control": {
|
||||
"tcpSshProperties": {
|
||||
"address": "127.0.0.2",
|
||||
"port": 0
|
||||
},
|
||||
"ipid": "03",
|
||||
"method": "ipidTcp"
|
||||
},
|
||||
"devices": [
|
||||
{
|
||||
"deviceKey": "panasonicDisplay01",
|
||||
"joinStart": 1
|
||||
},
|
||||
{
|
||||
"deviceKey": "vtcComPort",
|
||||
"joinStart": 51
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have four Essentials Devices configured:
|
||||
|
||||
1. Pro3 with a Key of "processor"
|
||||
|
||||
1. Panasonic Display with a Key of "panasonicDisplay01"
|
||||
|
||||
1. Com port with a Key of "vtcComPort"
|
||||
|
||||
1. Bridge with a Key of "deviceBridge"
|
||||
|
||||
We want to have access to the com port for VTC Control from Simpl Windows and we want to control the display from Simpl Windows. To accomplish this, we have created a bridge device and added the devices to be bridged to the "devices" array on the bridge. As you can see we define the device key and the join start, which will determine which joins we will use on the resulting EISC to interact with the devices. In the Bridge control properties we defined ipid 03, and we will need a corresponding Ethernet System Intercommunication in the Simpl Windows program at ipid 03.
|
||||
|
||||
Now that our devices have been built, we can refer to the device join maps to see which joins correspond to which actions.
|
||||
|
||||
See below:
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Bridges
|
||||
{
|
||||
public class DisplayControllerJoinMap : JoinMapBase
|
||||
{
|
||||
#region Digitals
|
||||
/// <summary>
|
||||
/// Turns the display off and reports power off feedback
|
||||
/// </summary>
|
||||
public uint PowerOff { get; set; }
|
||||
/// <summary>
|
||||
/// Turns the display on and repots power on feedback
|
||||
/// </summary>
|
||||
public uint PowerOn { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates that the display device supports two way communication when high
|
||||
/// </summary>
|
||||
public uint IsTwoWayDisplay { get; set; }
|
||||
/// <summary>
|
||||
/// Increments the volume while high
|
||||
/// </summary>
|
||||
public uint VolumeUp { get; set; }
|
||||
/// <summary>
|
||||
/// Decrements teh volume while high
|
||||
/// </summary>
|
||||
public uint VolumeDown { get; set; }
|
||||
/// <summary>
|
||||
/// Toggles the mute state. Feedback is high when volume is muted
|
||||
/// </summary>
|
||||
public uint VolumeMute { get; set; }
|
||||
/// <summary>
|
||||
/// Range of digital joins to select inputs and report current input as feedback
|
||||
/// </summary>
|
||||
public uint InputSelectOffset { get; set; }
|
||||
/// <summary>
|
||||
/// Range of digital joins to report visibility for input buttons
|
||||
/// </summary>
|
||||
public uint ButtonVisibilityOffset { get; set; }
|
||||
/// <summary>
|
||||
/// High if the device is online
|
||||
/// </summary>
|
||||
public uint IsOnline { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Analogs
|
||||
/// <summary>
|
||||
/// Analog join to set the input and report current input as feedback
|
||||
/// </summary>
|
||||
public uint InputSelect { get; set; }
|
||||
/// <summary>
|
||||
/// Sets the volume level and reports the current level as feedback
|
||||
/// </summary>
|
||||
public uint VolumeLevel { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Serials
|
||||
/// <summary>
|
||||
/// Reports the name of the display as defined in config as feedback
|
||||
/// </summary>
|
||||
public uint Name { get; set; }
|
||||
/// <summary>
|
||||
/// Range of serial joins that reports the names of the inputs as feedback
|
||||
/// </summary>
|
||||
public uint InputNamesOffset { get; set; }
|
||||
#endregion
|
||||
|
||||
public DisplayControllerJoinMap()
|
||||
{
|
||||
// Digital
|
||||
IsOnline = 50;
|
||||
PowerOff = 1;
|
||||
PowerOn = 2;
|
||||
IsTwoWayDisplay = 3;
|
||||
VolumeUp = 5;
|
||||
VolumeDown = 6;
|
||||
VolumeMute = 7;
|
||||
|
||||
ButtonVisibilityOffset = 40;
|
||||
InputSelectOffset = 10;
|
||||
|
||||
// Analog
|
||||
InputSelect = 11;
|
||||
VolumeLevel = 5;
|
||||
|
||||
// Serial
|
||||
Name = 1;
|
||||
InputNamesOffset = 10;
|
||||
}
|
||||
|
||||
public override void OffsetJoinNumbers(uint joinStart)
|
||||
{
|
||||
var joinOffset = joinStart - 1;
|
||||
|
||||
IsOnline = IsOnline + joinOffset;
|
||||
PowerOff = PowerOff + joinOffset;
|
||||
PowerOn = PowerOn + joinOffset;
|
||||
IsTwoWayDisplay = IsTwoWayDisplay + joinOffset;
|
||||
ButtonVisibilityOffset = ButtonVisibilityOffset + joinOffset;
|
||||
Name = Name + joinOffset;
|
||||
InputNamesOffset = InputNamesOffset + joinOffset;
|
||||
InputSelectOffset = InputSelectOffset + joinOffset;
|
||||
|
||||
InputSelect = InputSelect + joinOffset;
|
||||
|
||||
VolumeUp = VolumeUp + joinOffset;
|
||||
VolumeDown = VolumeDown + joinOffset;
|
||||
VolumeMute = VolumeMute + joinOffset;
|
||||
VolumeLevel = VolumeLevel + joinOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We know that the Panasonic Display uses the DisplayControllerJoinMap class and can see the join numbers that will give us access to functionality in the Device.
|
||||
|
||||
IsOnline = 50
|
||||
PowerOff = 1
|
||||
PowerOn = 2
|
||||
IsTwoWayDisplay = 3
|
||||
VolumeUp = 5
|
||||
VolumeDown = 6
|
||||
VolumeMute = 7
|
||||
|
||||
```cs
|
||||
namespace PepperDash.Essentials.Bridges
|
||||
{
|
||||
public class IBasicCommunicationJoinMap : JoinMapBase
|
||||
{
|
||||
#region Digitals
|
||||
/// <summary>
|
||||
/// Set High to connect, Low to disconnect
|
||||
/// </summary>
|
||||
public uint Connect { get; set; }
|
||||
/// <summary>
|
||||
/// Reports Connected State (High = Connected)
|
||||
/// </summary>
|
||||
public uint Connected { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Analogs
|
||||
/// <summary>
|
||||
/// Reports the connections status value
|
||||
/// </summary>
|
||||
public uint Status { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Serials
|
||||
/// <summary>
|
||||
/// Data back from port
|
||||
/// </summary>
|
||||
public uint TextReceived { get; set; }
|
||||
/// <summary>
|
||||
/// Sends data to the port
|
||||
/// </summary>
|
||||
public uint SendText { get; set; }
|
||||
/// <summary>
|
||||
/// Takes a JSON serialized string that sets a COM port's parameters
|
||||
/// </summary>
|
||||
public uint SetPortConfig { get; set; }
|
||||
#endregion
|
||||
|
||||
public IBasicCommunicationJoinMap()
|
||||
{
|
||||
TextReceived = 1;
|
||||
SendText = 1;
|
||||
SetPortConfig = 2;
|
||||
Connect = 1;
|
||||
Connected = 1;
|
||||
Status = 1;
|
||||
}
|
||||
|
||||
public override void OffsetJoinNumbers(uint joinStart)
|
||||
{
|
||||
var joinOffset = joinStart - 1;
|
||||
|
||||
TextReceived = TextReceived + joinOffset;
|
||||
SendText = SendText + joinOffset;
|
||||
SetPortConfig = SetPortConfig + joinOffset;
|
||||
Connect = Connect + joinOffset;
|
||||
Connected = Connected + joinOffset;
|
||||
Status = Status + joinOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
TextReceived = 1
|
||||
SendText = 1
|
||||
SetPortConfig = 2
|
||||
Connect = 1
|
||||
Connected = 1
|
||||
Status = 1
|
||||
|
||||
Considering our Bridge config, we can see that the display controls will start at join 1, and the VTC Com port will start at join 51. The result is a single EISC that allows us to interact with our Essentials devices.
|
||||
|
||||
To control diplay power from Simpl Windows, we would connect Digital Signals to joins 1 & 2 on the EISC to control Display Power On & Off.
|
||||
To utilize the com port device, we would connect Serial Signals (VTC_TX$ and VTC_RX$) to join 51 on the EISC.
|
||||
|
||||
You can refer to our [Simpl Windows Bridging Example](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for a more complex example.
|
||||
Example device config: <https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json>
|
||||
|
||||
## Notes
|
||||
|
||||
1. It is important to realize that there are no safety checks (yet) when assigning joinStarts in bridge configurations. If you were to put two devices on a bridge with overlapping joins, the most recently bridged join would overwrite previously bridged joins. For now it is on the programmer to ensure there are no conflicting join maps.
|
||||
|
||||
1. There is _no_ limit to the amount of times a device may be bridged to. You may have the same device on multiple bridges across multiple applications without problem. That being said, we recommend using common sense. Accessing a single com port for VTC control via multiple bridges may not be wise...
|
||||
|
||||
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||
|
||||
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||
|
||||
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
||||
|
||||
1. Related to item 5, you can use the same paradigm with respect to physical device communication. If you were to have a DSP device in some rooms communicating over RS232 and some via SSH, it would be trival to swap the device from a Com port to an SSH client in the Essentials Devicee Config and update the Bridge Config to brigde to the desired communication method. Again this would require no change on the Simpl Windows side as long as you maintain the same join Start in the Bridge Device Configuration.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. There are 10 conference rooms that all operate the same, but have hardware differences that are impossible to account for in SIMPL Windows. For example, each room might have a DM-MD8X8 chassis, but the input and output cards aren't all in the same order, or they might be different models but function the same. You can use Essentials with a unique configuration file for each hardware configuration.
|
||||
|
||||
1. You have a floor of conference rooms that all share some centralized hardware like DSP, AV Routing and a shared CEN-GWEXER gateway with multiple GLS-OIR-CSM-EX-BATT occupancy sensors. All the shared hardware can be defined in the Essentials configuration and bridged over an EISC to each program that needs access. The same device can even be exposed to multiple programs over different EISCs.
|
||||
|
||||
1. You have a SIMPL program that works for many room types, but because some rooms have different models of processors than others (CP3/CP3N/AV3/PRO3/DMPS3 variants), you have to maintain several versions of the program, compiled for each processor model to maintain access to features like the System Monitor slot. You can use Essentials running in a slot on a processor to expose the System Monitor and many other features of the processor, regardless of model. Now you only need to maintain a single SIMPL program defined for your most complex processor application (ex. PRO3)
|
||||
|
||||
## Device Type Join Maps
|
||||
|
||||
### AirMediaController
|
||||
|
||||
> supports: AM-200, AM-300
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/AirMediaControllerJoinMap.cs>
|
||||
|
||||
### AppleTvController
|
||||
|
||||
> supports: IR control of Apple TV
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/AppleTvJoinMap.cs>
|
||||
|
||||
### CameraControlBase
|
||||
|
||||
> supports: any camera that derives from CameraBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/CameraControllerJoinMap.cs>
|
||||
|
||||
### DisplayController
|
||||
|
||||
> supports: IR controlled displays, any two way display driver that derives from PepperDash.Essentials.Core.DisplayBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DisplayControllerJoinMap.cs>
|
||||
|
||||
### DmChasisController
|
||||
|
||||
> supports: All DM-MD-8x8/16x16/32x32 chassis, with or w/o DM-CPU3 Card
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmChassisControllerJoinMap.cs>
|
||||
|
||||
### DmRmcController
|
||||
|
||||
> supports: All DM-RMC devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmRmcControllerJoinMap.cs>
|
||||
|
||||
### DmTxController
|
||||
|
||||
> supports: All Dm-Tx devices
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmTxControllerJoinMap.cs>
|
||||
|
||||
### DmpsAudioOutputController
|
||||
|
||||
> supports: Program, Aux1, Aux2 outputs of all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmpsAudioOutputControllerJoinMap.cs>
|
||||
|
||||
### DmpsRoutingController
|
||||
|
||||
> supports: Av routing for all DMPS3 Control Systems
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/DmpsRoutingControllerJoinMap.cs>
|
||||
|
||||
### GenericRelayController
|
||||
|
||||
> supports: Any relay port on a Crestron Control System or Dm Endpoint
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GenericRelayControllerJoinMap.cs>
|
||||
|
||||
### GenericLightingJoinMap
|
||||
|
||||
> supports: Devices derived from PepperDash.Essentials.Core.Lighting.LightingBase
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GenericLightingJoinMap.cs>
|
||||
|
||||
### GlsOccupancySensorBase
|
||||
|
||||
> supports: Any Crestron GLS-Type Occupancy sensor - single/dual type
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/GlsOccupancySensorBaseJoinMap.cs>
|
||||
|
||||
### HdMdxxxCEController
|
||||
|
||||
> supports: HD-MD-400-C-E, HD-MD-300-C-E, HD-MD-200-C-E, HD-MD-200-C-1G-E-B/W
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/HdMdxxxCEControllerJoinMap.cs>
|
||||
|
||||
### IBasicCommunication
|
||||
|
||||
> supports: Any COM Port on a Control System or Dm Endpoint device, TCP Client, SSH Client, or UDP Server
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/IBasicCommunicationJoinMap.cs>
|
||||
|
||||
### IDigitalInput
|
||||
|
||||
> supports: Any Digital Input on a Control System, or DM Endpoint device
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/IDigitalInputJoinMap.cs>
|
||||
|
||||
### SystemMonitorController
|
||||
|
||||
> supports: Exposing the system monitor slot for any Control System
|
||||
|
||||
<https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Bridges/JoinMaps/SystemMonitorJoinMap.cs>
|
||||
|
||||
## Example SIMPL Windows Program
|
||||
|
||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
19
docs/docs/Standalone-Use.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Stand-alone Application
|
||||
|
||||
Essentials was originally designed as a standalone SIMPL# Pro control system application and has developed into a versatile, pluggable application. This page describes how to use our built-in room types for a completely self-contained "one-slot" control program.
|
||||
|
||||
By defining devices and a room in a JSON configuration file, Essentials can control an entire AV control system for a room. A file can be manually created in an IDE such as Visual Studio Code, or it can be generated by a friendly web-based configuration tool on [PepperDash Portal](http://pepperdash.com/products/), or some other configuration tool application, both requiring no knowledge of JSON. These tools step a user through building the necessary devices and setting to achieve a full working room.
|
||||
|
||||
## Plugins
|
||||
|
||||
### Devices
|
||||
|
||||
Essentials supports device plugins for communicating with various devices using both standard Crestron CIP communications, Cresnet, SSH, or other TCP/IP-based communication methods. See [the Plugins section](~/docs/Plugins.md) for more details
|
||||
|
||||
### Rooms
|
||||
|
||||
In order to tie together equipment into a unit that comprises what devices are used in a room, Essentials supports Room plugins. These plugins are similar to device plugins, in that they're loaded at runtime and allow for customization of business logic and behavior. They're loaded into a different section of the Device Manager, and can reference devices created by device plugins using the device's key.
|
||||
|
||||
See Also: [[Supported Devices|Supported-Devices]]
|
||||
|
||||
Next: [Simpl Windows bridging](~/docs/SIMPL-Bridging-Updated.md)
|
||||
68
docs/docs/Supported-Devices.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Essentials Framework Devices by Type
|
||||
|
||||
## Cameras
|
||||
|
||||
* VISCA protocol
|
||||
* Cisco (via codec)
|
||||
* Zoom (via Zoom Room)
|
||||
|
||||
## Disc Player
|
||||
|
||||
* Any IR disc player that implements standard RAD commands
|
||||
|
||||
## Displays
|
||||
|
||||
* Any IR display that implements standard RAD commands
|
||||
* Samsung MDC protocol (commercial)
|
||||
* NEC Professional series flat panel
|
||||
* Avocor VTF
|
||||
* Panasonic TH series flat panels
|
||||
* Panasonic Projectors [(via plugin)](https://github.com/PepperDash/epi-display-panasonic-projectors)
|
||||
* LG Commercial series [(via plugin)](https://github.com/PepperDash/epi-display-lg)
|
||||
* Generic CEC control via HDMI [(via plugin)](https://github.com/PepperDash/epi-generic-cec-display)
|
||||
* Crestron Certified Driver Display [(via plugin)](https://github.com/batourin/epi-display-ccd)
|
||||
|
||||
## Lighting/Shading
|
||||
|
||||
* Lutron Quantum
|
||||
* Somfy Shades (relay control)
|
||||
|
||||
## Power Controllers
|
||||
|
||||
* Digital Logger
|
||||
|
||||
## Set Top Boxes
|
||||
|
||||
* Any IR set top box that implements standard RAD commands
|
||||
|
||||
## Streaming Players
|
||||
|
||||
* AppleTV (IR)
|
||||
* Roku (IR)
|
||||
|
||||
## Video Codecs
|
||||
|
||||
* Cisco CE series (C/SX/RoomKit)
|
||||
* Zoom Room
|
||||
|
||||
## DSPs / Audio Codecs
|
||||
|
||||
* BiAmp Tesira [(via plugin](https://github.com/PepperDash/epi-dsp-tesira)
|
||||
|
||||
## Crestron Devices
|
||||
|
||||
* AM-200/300 Airmedia
|
||||
* All DM Chassis (8x8 * 128x128)
|
||||
* All DM input/output cards
|
||||
* All DMPS Processors
|
||||
* All DM Transmitter models (with COM/IR/Relay/CEC port access)
|
||||
* All DM Receiver models (with COM/IR/Relay/CEC port access)
|
||||
* DGE-100
|
||||
* DM-DGE-200-C
|
||||
* DIN-8SW8
|
||||
* CEN-IO-DIGIN-104
|
||||
* CEN-RFGWEX/GWEXER
|
||||
* GLS-ODT/OIR-C-CN Occupancy Sensors
|
||||
* TSW-XXX series touchpanels
|
||||
* XPanel for SmartGraphics
|
||||
* Fusion Room and Assets
|
||||
1
docs/docs/getting-started.md
Normal file
@@ -0,0 +1 @@
|
||||
# Getting Started
|
||||
|
Before Width: | Height: | Size: 398 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 337 KiB After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
BIN
docs/docs/images/essentials_bug_blue-500.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/docs/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
docs/docs/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 648 KiB After Width: | Height: | Size: 648 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
48
docs/docs/toc.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
- name: Get Started With Essentials
|
||||
- href: ../index.md
|
||||
- href: Get-started.md
|
||||
- name: Usage
|
||||
items:
|
||||
- href: Standalone-Use.md
|
||||
- href: SIMPL-Bridging-Updated.md
|
||||
items:
|
||||
- name: Join Maps
|
||||
href: JoinMaps.md
|
||||
- name: Bridging to Hardware Resources
|
||||
href: Bridging-To-Hardware-Resources.md
|
||||
items:
|
||||
- name: GenericComm Bridging
|
||||
href: GenericComm.md
|
||||
- name: RelayOutput Bridging
|
||||
href: RelayOutput.md
|
||||
- name: Digital Input Bridging
|
||||
href: DigitalInput.md
|
||||
- name: IR Driver Bridging
|
||||
href: IR-Driver-Bridging.md
|
||||
- name: Technical documentation
|
||||
items:
|
||||
- href: Arch-summary.md
|
||||
- name: Devices and DeviceManager
|
||||
href: Arch-1.md
|
||||
- name: Configurable lifecycle
|
||||
href: Arch-lifecycle.md
|
||||
- name: Activation phases
|
||||
href: Arch-activate.md
|
||||
- name: More
|
||||
href: Arch-topics.md
|
||||
- name: Plugins
|
||||
href: Plugins.md
|
||||
- name: Communication Basics
|
||||
href: Communication-Basics.md
|
||||
- name: Debugging
|
||||
href: Debugging.md
|
||||
- name: Feedback Classes
|
||||
href: Feedback-Classes.md
|
||||
- name: Connection Based Routing
|
||||
href: Connection-Based-Routing.md
|
||||
- name: Configuration Structure
|
||||
href: ConfigurationStructure.md
|
||||
- name: Supported Devices
|
||||
href: Supported-Devices.md
|
||||
- name: Glossary of Terms
|
||||
href: Glossary-of-Terms.md
|
||||
59
docs/index.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Welcome to PepperDash Essentials!
|
||||
|
||||
PepperDash Essentials is an open-source framework for control systems, built on Crestron's Simpl# Pro framework. It can be configured as a standalone program capable of running a wide variety of system designs and can also be used to augment other Crestron programs.
|
||||
|
||||
Essentials is a collection of C# libraries that can be used in many ways. It is a 100% configuration-driven framework that can be extended to add different workflows and behaviors, either through the addition of new device-types and classes, or via a plug-in mechanism. The framework is a collection of things that are all related and interconnected, but in general do not have strong dependencies on each other.
|
||||
|
||||
---
|
||||
|
||||
## Get started
|
||||
|
||||
- [Download essentials build or clone repo](~/docs/Get-started.md)
|
||||
- [How to get started](~/docs/Get-started.md)
|
||||
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
|
||||
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
|
||||
|
||||
Or use the links to the right to navigate our documentation.
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
- Runs on Crestron 3-Series, **4-Series** and VC-4 Control System platforms
|
||||
- Reduced hardware overhead compared to S+ and Simpl solutions
|
||||
- Quick development cycle
|
||||
- Shared resources made easily available
|
||||
- More flexibility with less code
|
||||
- Configurable using simple JSON files
|
||||
- Is awesome
|
||||
|
||||
---
|
||||
|
||||
## Comment
|
||||
|
||||
The Essentials wiki is clearly in-progress right now. Take a look at the links to the right. We are actively working on this documentation, so please be patient with us. If you have any comments on or suggestions for the documentation, please file an issue here, with as much detail as you can provide: <https://github.com/PepperDash/Essentials/issues>
|
||||
|
||||
Thanks!
|
||||
|
||||
---
|
||||
|
||||
## Collaboration
|
||||
|
||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
|
||||
|
||||
### Open-source-collaborative workflow
|
||||
|
||||
The `main` branch always contain the latest stable version. The `development` branch is used for most development efforts.
|
||||
|
||||
[GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) will be used as the workflow for this collaborative project. To contribute, follow this process:
|
||||
|
||||
1. Fork this repository ([More Info](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks))
|
||||
2. Create a branch using standard GitFlow branch prefixes (feature/hotfix) followed by a descriptive name.
|
||||
- Example: `feature/add-awesomeness` or `hotfix/really-big-oops`
|
||||
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
|
||||
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
|
||||
4. When the scope of the work for your branch is complete, make sure to rebase your branch in case further progress has been made since the repo was forked
|
||||
5. Create a Pull Request to pull your branch into the appropriate branch in the main repository.
|
||||
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.
|
||||
|
||||
Next: [Get started](~/docs/Get-started.md)
|
||||
4
docs/toc.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- name: Docs
|
||||
href: docs/
|
||||
- name: API
|
||||
href: api/
|
||||
7
runtimeconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"configProperties": {
|
||||
"System.Globalization.Invariant": false
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/CrestronMock/CCriticalSection.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Mock implementation of Crestron CCriticalSection for testing purposes
|
||||
/// Provides the same public API surface as the real CCriticalSection
|
||||
/// </summary>
|
||||
public class CCriticalSection : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private readonly object _lockObject = new object();
|
||||
private readonly ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
|
||||
private bool _disposed = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>Initializes a new instance of the CCriticalSection class</summary>
|
||||
public CCriticalSection()
|
||||
{
|
||||
// Mock implementation - no actual initialization required
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>Enters the critical section</summary>
|
||||
public void Enter()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CCriticalSection));
|
||||
Monitor.Enter(_lockObject);
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter the critical section</summary>
|
||||
/// <returns>True if the critical section was entered successfully</returns>
|
||||
public bool TryEnter()
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return Monitor.TryEnter(_lockObject);
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter the critical section with a timeout</summary>
|
||||
/// <param name="timeout">Timeout in milliseconds</param>
|
||||
/// <returns>True if the critical section was entered successfully</returns>
|
||||
public bool TryEnter(int timeout)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return Monitor.TryEnter(_lockObject, timeout);
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter the critical section with a TimeSpan timeout</summary>
|
||||
/// <param name="timeout">Timeout as TimeSpan</param>
|
||||
/// <returns>True if the critical section was entered successfully</returns>
|
||||
public bool TryEnter(TimeSpan timeout)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return Monitor.TryEnter(_lockObject, timeout);
|
||||
}
|
||||
|
||||
/// <summary>Leaves the critical section</summary>
|
||||
public void Leave()
|
||||
{
|
||||
if (_disposed) return;
|
||||
try
|
||||
{
|
||||
Monitor.Exit(_lockObject);
|
||||
}
|
||||
catch (SynchronizationLockException)
|
||||
{
|
||||
// Ignore if not held by current thread
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enters a read lock</summary>
|
||||
public void EnterReadLock()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CCriticalSection));
|
||||
_readerWriterLock.EnterReadLock();
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter a read lock</summary>
|
||||
/// <returns>True if the read lock was acquired successfully</returns>
|
||||
public bool TryEnterReadLock()
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return _readerWriterLock.TryEnterReadLock(0);
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter a read lock with a timeout</summary>
|
||||
/// <param name="timeout">Timeout in milliseconds</param>
|
||||
/// <returns>True if the read lock was acquired successfully</returns>
|
||||
public bool TryEnterReadLock(int timeout)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return _readerWriterLock.TryEnterReadLock(timeout);
|
||||
}
|
||||
|
||||
/// <summary>Exits the read lock</summary>
|
||||
public void ExitReadLock()
|
||||
{
|
||||
if (_disposed) return;
|
||||
try
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
}
|
||||
catch (SynchronizationLockException)
|
||||
{
|
||||
// Ignore if not held by current thread
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enters a write lock</summary>
|
||||
public void EnterWriteLock()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CCriticalSection));
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter a write lock</summary>
|
||||
/// <returns>True if the write lock was acquired successfully</returns>
|
||||
public bool TryEnterWriteLock()
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return _readerWriterLock.TryEnterWriteLock(0);
|
||||
}
|
||||
|
||||
/// <summary>Tries to enter a write lock with a timeout</summary>
|
||||
/// <param name="timeout">Timeout in milliseconds</param>
|
||||
/// <returns>True if the write lock was acquired successfully</returns>
|
||||
public bool TryEnterWriteLock(int timeout)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
return _readerWriterLock.TryEnterWriteLock(timeout);
|
||||
}
|
||||
|
||||
/// <summary>Exits the write lock</summary>
|
||||
public void ExitWriteLock()
|
||||
{
|
||||
if (_disposed) return;
|
||||
try
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
}
|
||||
catch (SynchronizationLockException)
|
||||
{
|
||||
// Ignore if not held by current thread
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>Disposes the critical section and releases resources</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Protected dispose method</summary>
|
||||
/// <param name="disposing">True if disposing managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_readerWriterLock?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
191
src/CrestronMock/CTimer.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>Mock timer event handler</summary>
|
||||
/// <param name="userObject">User-defined object</param>
|
||||
public delegate void CTimerEventHandler(object? userObject);
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of Crestron CTimer for testing purposes
|
||||
/// Provides the same public API surface as the real CTimer
|
||||
/// </summary>
|
||||
public class CTimer : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Timer? _timer;
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed = false;
|
||||
private readonly CTimerEventHandler? _callback;
|
||||
private readonly object? _userObject;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>Gets whether the timer is currently running</summary>
|
||||
public bool Running { get; private set; } = false;
|
||||
|
||||
/// <summary>Gets the timer interval in milliseconds</summary>
|
||||
public long TimeToFire { get; private set; } = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>Initializes a new instance of the CTimer class</summary>
|
||||
/// <param name="callbackFunction">Function to call when timer fires</param>
|
||||
/// <param name="userObject">User-defined object to pass to callback</param>
|
||||
/// <param name="dueTime">Time before timer first fires (milliseconds)</param>
|
||||
/// <param name="period">Interval between timer fires (milliseconds), or -1 for one-shot</param>
|
||||
public CTimer(CTimerEventHandler callbackFunction, object? userObject, long dueTime, long period)
|
||||
{
|
||||
_callback = callbackFunction;
|
||||
_userObject = userObject;
|
||||
TimeToFire = period;
|
||||
|
||||
var dueTimeInt = dueTime > int.MaxValue ? Timeout.Infinite : (int)dueTime;
|
||||
var periodInt = period > int.MaxValue || period < 0 ? Timeout.Infinite : (int)period;
|
||||
|
||||
_timer = new Timer(TimerCallback, null, dueTimeInt, periodInt);
|
||||
Running = dueTime != Timeout.Infinite;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the CTimer class</summary>
|
||||
/// <param name="callbackFunction">Function to call when timer fires</param>
|
||||
/// <param name="userObject">User-defined object to pass to callback</param>
|
||||
/// <param name="dueTime">Time before timer first fires (milliseconds)</param>
|
||||
public CTimer(CTimerEventHandler callbackFunction, object? userObject, long dueTime)
|
||||
: this(callbackFunction, userObject, dueTime, -1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the CTimer class</summary>
|
||||
/// <param name="callbackFunction">Function to call when timer fires</param>
|
||||
/// <param name="userObject">User-defined object to pass to callback</param>
|
||||
public CTimer(CTimerEventHandler callbackFunction, object? userObject)
|
||||
: this(callbackFunction, userObject, Timeout.Infinite, -1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the CTimer class</summary>
|
||||
/// <param name="callbackFunction">Function to call when timer fires</param>
|
||||
public CTimer(CTimerEventHandler callbackFunction)
|
||||
: this(callbackFunction, null, Timeout.Infinite, -1)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>Resets the timer with a new due time</summary>
|
||||
/// <param name="dueTime">New due time in milliseconds</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public bool Reset(long dueTime)
|
||||
{
|
||||
return Reset(dueTime, -1);
|
||||
}
|
||||
|
||||
/// <summary>Resets the timer with new due time and period</summary>
|
||||
/// <param name="dueTime">New due time in milliseconds</param>
|
||||
/// <param name="period">New period in milliseconds, or -1 for one-shot</param>
|
||||
/// <returns>True if successful</returns>
|
||||
public bool Reset(long dueTime, long period)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_disposed || _timer == null) return false;
|
||||
|
||||
TimeToFire = period;
|
||||
var dueTimeInt = dueTime > int.MaxValue ? Timeout.Infinite : (int)dueTime;
|
||||
var periodInt = period > int.MaxValue || period < 0 ? Timeout.Infinite : (int)period;
|
||||
|
||||
try
|
||||
{
|
||||
_timer.Change(dueTimeInt, periodInt);
|
||||
Running = dueTime != Timeout.Infinite;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stops the timer</summary>
|
||||
/// <returns>True if successful</returns>
|
||||
public bool Stop()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_disposed || _timer == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
Running = false;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>Internal timer callback</summary>
|
||||
/// <param name="state">Timer state (unused)</param>
|
||||
private void TimerCallback(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
_callback?.Invoke(_userObject);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Suppress exceptions in callback to prevent timer from stopping
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>Disposes the timer and releases resources</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Protected dispose method</summary>
|
||||
/// <param name="disposing">True if disposing managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
Running = false;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
119
src/CrestronMock/Console.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
public static class ErrorLog
|
||||
{
|
||||
public static void Error(string message, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] {string.Format(message, args)}");
|
||||
}
|
||||
|
||||
public static void Notice(string message, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"[NOTICE] {string.Format(message, args)}");
|
||||
}
|
||||
|
||||
public static void Warn(string message, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"[WARN] {string.Format(message, args)}");
|
||||
}
|
||||
|
||||
public static void Info(string message, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"[INFO] {string.Format(message, args)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronDataStore
|
||||
{
|
||||
public static class CrestronDataStoreStatic
|
||||
{
|
||||
public static CDS_ERROR SetLocalStringValue(string key, string value)
|
||||
{
|
||||
return CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
public static CDS_ERROR GetLocalStringValue(string key, out string value)
|
||||
{
|
||||
value = "";
|
||||
return CDS_ERROR.CDS_SUCCESS;
|
||||
}
|
||||
|
||||
/// <summary>Initialize the Crestron data store</summary>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int InitCrestronDataStore()
|
||||
{
|
||||
// Mock implementation
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Get a boolean value from local storage</summary>
|
||||
/// <param name="key">The key to retrieve</param>
|
||||
/// <param name="value">The retrieved value</param>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int GetLocalBoolValue(string key, out bool value)
|
||||
{
|
||||
// Mock implementation - always return false for now
|
||||
value = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Set a boolean value in local storage</summary>
|
||||
/// <param name="key">The key to set</param>
|
||||
/// <param name="value">The value to set</param>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int SetLocalBoolValue(string key, bool value)
|
||||
{
|
||||
// Mock implementation
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Get an integer value from local storage</summary>
|
||||
/// <param name="key">The key to retrieve</param>
|
||||
/// <param name="value">The retrieved value</param>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int GetLocalIntValue(string key, out int value)
|
||||
{
|
||||
// Mock implementation - always return 0 for now
|
||||
value = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Set an integer value in local storage</summary>
|
||||
/// <param name="key">The key to set</param>
|
||||
/// <param name="value">The value to set</param>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int SetLocalIntValue(string key, int value)
|
||||
{
|
||||
// Mock implementation
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Set an unsigned integer value in local storage</summary>
|
||||
/// <param name="key">The key to set</param>
|
||||
/// <param name="value">The value to set</param>
|
||||
/// <returns>0 on success, negative on error</returns>
|
||||
public static int SetLocalUintValue(string key, uint value)
|
||||
{
|
||||
// Mock implementation
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CDS_ERROR
|
||||
{
|
||||
CDS_SUCCESS = 0,
|
||||
CDS_ERROR = -1
|
||||
}
|
||||
|
||||
/// <summary>Mock CrestronDataStore for local data storage</summary>
|
||||
public static class CrestronDataStore
|
||||
{
|
||||
/// <summary>Error constant for CDS operations</summary>
|
||||
public static readonly int CDS_ERROR = -1;
|
||||
}
|
||||
}
|
||||
27
src/CrestronMock/ConsoleTypes.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>Mock console command response utility</summary>
|
||||
public static class ConsoleCommandResponseUtility
|
||||
{
|
||||
/// <summary>Send console command response with response code</summary>
|
||||
/// <param name="response">The response text</param>
|
||||
/// <param name="responseCode">The response code</param>
|
||||
public static void ConsoleCommandResponse(string response, int responseCode = 0)
|
||||
{
|
||||
// Mock implementation - just log to console or ignore
|
||||
Console.WriteLine($"Console Response ({responseCode}): {response}");
|
||||
}
|
||||
|
||||
/// <summary>Send console command response with additional parameter</summary>
|
||||
/// <param name="response">The response text</param>
|
||||
/// <param name="param1">First parameter</param>
|
||||
/// <param name="param2">Second parameter</param>
|
||||
public static void ConsoleCommandResponse(string response, object param1, object param2)
|
||||
{
|
||||
// Mock implementation
|
||||
Console.WriteLine($"Console Response: {response} - {param1}, {param2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
266
src/CrestronMock/CrestronControlSystem.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CrestronMock;
|
||||
|
||||
/// <summary>Mock Crestron signal base class</summary>
|
||||
public class Sig { }
|
||||
|
||||
/// <summary>Mock UShort input signal</summary>
|
||||
public class UShortInputSig { }
|
||||
|
||||
/// <summary>Mock UShort output signal</summary>
|
||||
public class UShortOutputSig { }
|
||||
|
||||
/// <summary>Mock Boolean input signal</summary>
|
||||
public class BoolInputSig { }
|
||||
|
||||
/// <summary>Mock String input signal</summary>
|
||||
public class StringInputSig { }
|
||||
|
||||
/// <summary>Mock Boolean output signal</summary>
|
||||
public class BoolOutputSig { }
|
||||
|
||||
/// <summary>Mock String output signal</summary>
|
||||
public class StringOutputSig { }
|
||||
|
||||
/// <summary>Mock signal group</summary>
|
||||
public class SigGroup { }
|
||||
|
||||
/// <summary>Mock COM port</summary>
|
||||
public class ComPort { }
|
||||
|
||||
/// <summary>Mock relay</summary>
|
||||
public class Relay { }
|
||||
|
||||
/// <summary>Mock IR output port</summary>
|
||||
public class IROutputPort { }
|
||||
|
||||
/// <summary>Mock IO port</summary>
|
||||
public class IOPort { }
|
||||
|
||||
/// <summary>Mock VersiPort</summary>
|
||||
public class VersiPort { }
|
||||
|
||||
/// <summary>Mock IR input port</summary>
|
||||
public class IRInputPort { }
|
||||
|
||||
/// <summary>Signal type enumeration</summary>
|
||||
public enum eSigType
|
||||
{
|
||||
Bool,
|
||||
UShort,
|
||||
String
|
||||
}
|
||||
|
||||
/// <summary>Mock read-only collection</summary>
|
||||
public class ReadOnlyCollection<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Mock COM ports interface</summary>
|
||||
public interface IComPorts
|
||||
{
|
||||
ComPort[] ComPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>Mock relay ports interface</summary>
|
||||
public interface IRelayPorts
|
||||
{
|
||||
Relay[] RelayPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>Mock IR output ports interface</summary>
|
||||
public interface IIROutputPorts
|
||||
{
|
||||
IROutputPort[] IROutputPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>Mock IO ports interface</summary>
|
||||
public interface IIOPorts
|
||||
{
|
||||
IOPort[] IOPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>Mock digital input ports interface</summary>
|
||||
public interface IDigitalInputPorts
|
||||
{
|
||||
VersiPort[] DigitalInputPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>Mock IR input port interface</summary>
|
||||
public interface IIRInputPort
|
||||
{
|
||||
IRInputPort IRInputPort { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of CrestronControlSystem for testing purposes
|
||||
/// Base class for the CrestronControlSystem The Customer application is derived over this class
|
||||
/// </summary>
|
||||
public class CrestronControlSystem : IComPorts, IRelayPorts, IIROutputPorts, IIOPorts, IDigitalInputPorts, IIRInputPort
|
||||
{
|
||||
// Static fields
|
||||
public static Sig NullCue { get; set; } = new Sig();
|
||||
public static UShortInputSig NullUShortInputSig { get; set; } = new UShortInputSig();
|
||||
public static UShortOutputSig NullUShortOutputSig { get; set; } = new UShortOutputSig();
|
||||
public static BoolInputSig NullBoolInputSig { get; set; } = new BoolInputSig();
|
||||
public static StringInputSig NullStringInputSig { get; set; } = new StringInputSig();
|
||||
public static BoolOutputSig NullBoolOutputSig { get; set; } = new BoolOutputSig();
|
||||
public static StringOutputSig NullStringOutputSig { get; set; } = new StringOutputSig();
|
||||
public static ReadOnlyCollection<int, SigGroup> SigGroups { get; set; } = new ReadOnlyCollection<int, SigGroup>();
|
||||
public static int MaxNumberOfEventsInQueue { get; set; } = 1000;
|
||||
|
||||
// Constructor
|
||||
public CrestronControlSystem()
|
||||
{
|
||||
// Initialize collections and properties
|
||||
ComPorts = Array.Empty<ComPort>();
|
||||
RelayPorts = Array.Empty<Relay>();
|
||||
IROutputPorts = Array.Empty<IROutputPort>();
|
||||
IOPorts = Array.Empty<IOPort>();
|
||||
DigitalInputPorts = Array.Empty<VersiPort>();
|
||||
IRInputPort = new IRInputPort();
|
||||
}
|
||||
|
||||
// Virtual methods that can be overridden
|
||||
public virtual void InitializeSystem()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void SavePreset()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void RecallPreset()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void BassFlat()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void TrebleFlat()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void LimiterEnable()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void LimiterDisable()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void LimiterSoftKneeOn()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void LimiterSoftKneeOff()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void MasterMuteOn()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void MasterMuteOff()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void MicMasterMuteOn()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void MicMasterMuteOff()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void SourceMuteOn()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
public virtual void SourceMuteOff()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
// Non-virtual methods
|
||||
public void MicMuteOn(uint MicNumber)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public void MicMuteOff(uint MicNumber)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public void MonoOutput()
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public void StereoOutput()
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Static methods for SigGroup management
|
||||
public static SigGroup CreateSigGroup(int groupID, params BoolInputSig[] boolInputSigs)
|
||||
{
|
||||
return new SigGroup();
|
||||
}
|
||||
|
||||
public static SigGroup CreateSigGroup(int groupID, params UShortInputSig[] ushortInputSigs)
|
||||
{
|
||||
return new SigGroup();
|
||||
}
|
||||
|
||||
public static SigGroup CreateSigGroup(int groupID, eSigType type)
|
||||
{
|
||||
return new SigGroup();
|
||||
}
|
||||
|
||||
public static SigGroup CreateSigGroup(int groupID, params StringInputSig[] stringInputSigs)
|
||||
{
|
||||
return new SigGroup();
|
||||
}
|
||||
|
||||
public static void RemoveSigGroup(int groupID)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public static void RemoveSigGroup(SigGroup sigGroupToRemove)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public static void ClearSigGroups()
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Interface implementations
|
||||
public ComPort[] ComPorts { get; set; }
|
||||
public Relay[] RelayPorts { get; set; }
|
||||
public IROutputPort[] IROutputPorts { get; set; }
|
||||
public IOPort[] IOPorts { get; set; }
|
||||
public VersiPort[] DigitalInputPorts { get; set; }
|
||||
public IRInputPort IRInputPort { get; set; }
|
||||
}
|
||||
277
src/CrestronMock/CrestronEnvironment.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
// Console and logging types needed by CrestronConsole and CrestronLogger
|
||||
public delegate string ConsoleCommandFunction(string parameters);
|
||||
|
||||
public enum ConsoleAccessLevelEnum
|
||||
{
|
||||
AccessOperator = 0,
|
||||
AccessProgrammer = 1,
|
||||
AccessAdministrator = 2
|
||||
}
|
||||
|
||||
public class ConsoleCommandParameterSpecClass
|
||||
{
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
/// <summary>Ethernet event handler delegate</summary>
|
||||
public delegate void EthernetEventHandler(EthernetEventArgs args);
|
||||
|
||||
/// <summary>Mock CrestronEnvironment for system event handling</summary>
|
||||
public static class CrestronEnvironment
|
||||
{
|
||||
/// <summary>Event fired when program status changes</summary>
|
||||
public static event ProgramStatusEventHandler? ProgramStatusEventHandler;
|
||||
|
||||
/// <summary>Event fired when ethernet status changes</summary>
|
||||
public static event EthernetEventHandler? EthernetEventHandler;
|
||||
|
||||
/// <summary>Gets the device platform</summary>
|
||||
public static eDevicePlatform DevicePlatform => eDevicePlatform.Appliance;
|
||||
|
||||
/// <summary>Gets the runtime environment</summary>
|
||||
public static eRuntimeEnvironment RuntimeEnvironment => eRuntimeEnvironment.SimplSharpPro;
|
||||
|
||||
/// <summary>Gets system information</summary>
|
||||
public static string SystemInfo => "Mock System v1.0";
|
||||
|
||||
/// <summary>Gets OS version</summary>
|
||||
public static string OSVersion => "Mock OS 1.0.0";
|
||||
|
||||
/// <summary>Gets new line character sequence</summary>
|
||||
public static string NewLine => Environment.NewLine;
|
||||
|
||||
/// <summary>Gets program compatibility level</summary>
|
||||
public static eProgramCompatibility ProgramCompatibility => eProgramCompatibility.Series3And4;
|
||||
|
||||
/// <summary>Sleep for specified milliseconds</summary>
|
||||
/// <param name="milliseconds">Sleep duration</param>
|
||||
public static void Sleep(int milliseconds)
|
||||
{
|
||||
System.Threading.Thread.Sleep(milliseconds);
|
||||
}
|
||||
|
||||
/// <summary>Gets the time zone</summary>
|
||||
/// <returns>Time zone string</returns>
|
||||
public static string GetTimeZone()
|
||||
{
|
||||
return TimeZoneInfo.Local.Id;
|
||||
}
|
||||
|
||||
/// <summary>Triggers a program status event (for testing)</summary>
|
||||
/// <param name="eventType">Event type</param>
|
||||
public static void TriggerProgramStatusEvent(eProgramStatusEventType eventType)
|
||||
{
|
||||
ProgramStatusEventHandler?.Invoke(eventType);
|
||||
}
|
||||
|
||||
/// <summary>Triggers an ethernet event (for testing)</summary>
|
||||
/// <param name="args">Event arguments</param>
|
||||
public static void TriggerEthernetEvent(EthernetEventArgs args)
|
||||
{
|
||||
EthernetEventHandler?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock ethernet event type enumeration</summary>
|
||||
public enum eEthernetEventType
|
||||
{
|
||||
/// <summary>Link down</summary>
|
||||
LinkDown = 0,
|
||||
/// <summary>Link up</summary>
|
||||
LinkUp = 1
|
||||
}
|
||||
|
||||
/// <summary>Mock CrestronConsole for console output</summary>
|
||||
public static class CrestronConsole
|
||||
{
|
||||
/// <summary>Prints a line to the console</summary>
|
||||
/// <param name="message">Message to print</param>
|
||||
public static void PrintLine(string message)
|
||||
{
|
||||
// Mock implementation - could write to System.Console in test environment
|
||||
Console.WriteLine($"[CrestronConsole] {message}");
|
||||
}
|
||||
|
||||
/// <summary>Prints formatted text to the console</summary>
|
||||
/// <param name="format">Format string</param>
|
||||
/// <param name="args">Arguments</param>
|
||||
public static void PrintLine(string format, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"[CrestronConsole] {string.Format(format, args)}");
|
||||
}
|
||||
|
||||
/// <summary>Prints text to the console without a newline</summary>
|
||||
/// <param name="message">Message to print</param>
|
||||
public static void Print(string message)
|
||||
{
|
||||
Console.Write($"[CrestronConsole] {message}");
|
||||
}
|
||||
|
||||
/// <summary>Console command response</summary>
|
||||
/// <param name="command">Command to execute</param>
|
||||
/// <returns>Response string</returns>
|
||||
public static string ConsoleCommandResponse(string command)
|
||||
{
|
||||
return $"Mock response for command: {command}";
|
||||
}
|
||||
|
||||
/// <summary>Add new console command</summary>
|
||||
/// <param name="function">Command function</param>
|
||||
/// <param name="command">Command name</param>
|
||||
/// <param name="help">Help text</param>
|
||||
/// <param name="accessLevel">Access level</param>
|
||||
/// <returns>0 for success</returns>
|
||||
public static int AddNewConsoleCommand(ConsoleCommandFunction function, string command, string help, ConsoleAccessLevelEnum accessLevel)
|
||||
{
|
||||
return 0; // Mock success
|
||||
}
|
||||
|
||||
/// <summary>Add new console command with parameter spec</summary>
|
||||
/// <param name="function">Command function</param>
|
||||
/// <param name="command">Command name</param>
|
||||
/// <param name="help">Help text</param>
|
||||
/// <param name="accessLevel">Access level</param>
|
||||
/// <param name="spec">Parameter specification</param>
|
||||
/// <returns>0 for success</returns>
|
||||
public static int AddNewConsoleCommand(ConsoleCommandFunction function, string command, string help, ConsoleAccessLevelEnum accessLevel, ConsoleCommandParameterSpecClass spec)
|
||||
{
|
||||
return 0; // Mock success
|
||||
}
|
||||
|
||||
/// <summary>Send control system command</summary>
|
||||
/// <param name="command">Command to send</param>
|
||||
/// <param name="programNumber">Program number</param>
|
||||
public static void SendControlSystemCommand(string command, uint programNumber)
|
||||
{
|
||||
// Mock implementation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronIO
|
||||
{
|
||||
/// <summary>Mock File class for basic file operations</summary>
|
||||
public static class File
|
||||
{
|
||||
/// <summary>Checks if a file exists</summary>
|
||||
/// <param name="path">File path</param>
|
||||
/// <returns>True if file exists</returns>
|
||||
public static bool Exists(string path)
|
||||
{
|
||||
// Mock implementation - use System.IO.File for actual file operations
|
||||
return System.IO.File.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>Reads all text from a file</summary>
|
||||
/// <param name="path">File path</param>
|
||||
/// <returns>File contents</returns>
|
||||
public static string ReadToEnd(string path)
|
||||
{
|
||||
return System.IO.File.ReadAllText(path);
|
||||
}
|
||||
|
||||
/// <summary>Reads all text from a file with specified encoding</summary>
|
||||
/// <param name="path">File path</param>
|
||||
/// <param name="encoding">Text encoding</param>
|
||||
/// <returns>File contents</returns>
|
||||
public static string ReadToEnd(string path, System.Text.Encoding encoding)
|
||||
{
|
||||
return System.IO.File.ReadAllText(path, encoding);
|
||||
}
|
||||
|
||||
/// <summary>Writes text to a file</summary>
|
||||
/// <param name="path">File path</param>
|
||||
/// <param name="contents">Contents to write</param>
|
||||
public static void WriteAllText(string path, string contents)
|
||||
{
|
||||
System.IO.File.WriteAllText(path, contents);
|
||||
}
|
||||
|
||||
/// <summary>Deletes a file</summary>
|
||||
/// <param name="path">File path</param>
|
||||
public static void Delete(string path)
|
||||
{
|
||||
if (System.IO.File.Exists(path))
|
||||
System.IO.File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock Directory class for basic directory operations</summary>
|
||||
public static class Directory
|
||||
{
|
||||
/// <summary>Gets the application directory path</summary>
|
||||
/// <returns>Application directory path</returns>
|
||||
public static string GetApplicationDirectory()
|
||||
{
|
||||
// Mock implementation - return current directory or a typical Crestron path
|
||||
return System.IO.Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
/// <summary>Gets the application root directory path</summary>
|
||||
/// <returns>Application root directory path</returns>
|
||||
public static string GetApplicationRootDirectory()
|
||||
{
|
||||
// Mock implementation - return current directory or a typical Crestron path
|
||||
return System.IO.Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
/// <summary>Checks if a directory exists</summary>
|
||||
/// <param name="path">Directory path</param>
|
||||
/// <returns>True if directory exists</returns>
|
||||
public static bool Exists(string path)
|
||||
{
|
||||
return System.IO.Directory.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>Creates a directory</summary>
|
||||
/// <param name="path">Directory path</param>
|
||||
public static void CreateDirectory(string path)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock Path class for path operations</summary>
|
||||
public static class Path
|
||||
{
|
||||
/// <summary>Directory separator character</summary>
|
||||
public static readonly char DirectorySeparatorChar = System.IO.Path.DirectorySeparatorChar;
|
||||
|
||||
/// <summary>Combines path strings</summary>
|
||||
/// <param name="path1">First path</param>
|
||||
/// <param name="path2">Second path</param>
|
||||
/// <returns>Combined path</returns>
|
||||
public static string Combine(string path1, string path2)
|
||||
{
|
||||
return System.IO.Path.Combine(path1, path2);
|
||||
}
|
||||
|
||||
/// <summary>Gets the file name from a path</summary>
|
||||
/// <param name="path">Full path</param>
|
||||
/// <returns>File name</returns>
|
||||
public static string GetFileName(string path)
|
||||
{
|
||||
return System.IO.Path.GetFileName(path);
|
||||
}
|
||||
|
||||
/// <summary>Gets the directory name from a path</summary>
|
||||
/// <param name="path">Full path</param>
|
||||
/// <returns>Directory name</returns>
|
||||
public static string GetDirectoryName(string path)
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>Gets the file extension from a path</summary>
|
||||
/// <param name="path">Full path</param>
|
||||
/// <returns>File extension</returns>
|
||||
public static string GetExtension(string path)
|
||||
{
|
||||
return System.IO.Path.GetExtension(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/CrestronMock/CrestronLogger.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronLogger
|
||||
{
|
||||
/// <summary>Mock CrestronLogger for .NET 8 compatibility</summary>
|
||||
public static class CrestronLogger
|
||||
{
|
||||
/// <summary>Write to log</summary>
|
||||
/// <param name="logName">Log name</param>
|
||||
/// <param name="message">Message</param>
|
||||
/// <param name="mode">Logger mode</param>
|
||||
public static void WriteToLog(string logName, string message, LoggerModeEnum mode)
|
||||
{
|
||||
Console.WriteLine($"[{logName}] {message}");
|
||||
}
|
||||
|
||||
/// <summary>Write to log with level</summary>
|
||||
/// <param name="message">Message</param>
|
||||
/// <param name="level">Log level</param>
|
||||
public static void WriteToLog(string message, uint level)
|
||||
{
|
||||
Console.WriteLine($"[Level {level}] {message}");
|
||||
}
|
||||
|
||||
/// <summary>Initialize logger</summary>
|
||||
/// <param name="bufferSize">Buffer size</param>
|
||||
/// <param name="mode">Logger mode</param>
|
||||
public static void Initialize(int bufferSize, LoggerModeEnum mode)
|
||||
{
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
/// <summary>Print the log</summary>
|
||||
/// <param name="includeAll">Include all log entries</param>
|
||||
/// <returns>Log entries as string list</returns>
|
||||
public static List<string> PrintTheLog(bool includeAll = false)
|
||||
{
|
||||
return new List<string> { "Mock log entry" };
|
||||
}
|
||||
|
||||
/// <summary>Clear the log</summary>
|
||||
/// <param name="clearAll">Clear all entries</param>
|
||||
/// <returns>Success message</returns>
|
||||
public static string Clear(bool clearAll)
|
||||
{
|
||||
return "Log cleared (mock)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Logger mode enumeration</summary>
|
||||
public enum LoggerModeEnum
|
||||
{
|
||||
/// <summary>Append mode</summary>
|
||||
LoggingModeAppend = 0,
|
||||
/// <summary>Overwrite mode</summary>
|
||||
LoggingModeOverwrite = 1,
|
||||
/// <summary>RM mode</summary>
|
||||
RM = 2
|
||||
}
|
||||
}
|
||||
10
src/CrestronMock/CrestronMock.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Configurations>Debug;Release;Test</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
245
src/CrestronMock/CrestronQueue.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Mock implementation of Crestron CrestronQueue for testing purposes
|
||||
/// Provides the same public API surface as the real CrestronQueue
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of items in the queue</typeparam>
|
||||
public class CrestronQueue<T> : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private readonly Queue<T> _queue = new Queue<T>();
|
||||
private readonly object _lockObject = new object();
|
||||
private readonly ManualResetEventSlim _dataAvailableEvent = new ManualResetEventSlim(false);
|
||||
private bool _disposed = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>Gets the number of items in the queue</summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the queue is empty</summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _queue.Count == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>Initializes a new instance of the CrestronQueue class</summary>
|
||||
public CrestronQueue()
|
||||
{
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the CrestronQueue class with specified capacity</summary>
|
||||
/// <param name="capacity">The initial capacity of the queue</param>
|
||||
public CrestronQueue(int capacity)
|
||||
{
|
||||
// Mock implementation - capacity is ignored in this mock
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>Adds an item to the end of the queue</summary>
|
||||
/// <param name="item">Item to add</param>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CrestronQueue<T>));
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_queue.Enqueue(item);
|
||||
_dataAvailableEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes and returns the item at the beginning of the queue</summary>
|
||||
/// <returns>The item that was removed from the queue</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the queue is empty</exception>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CrestronQueue<T>));
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
throw new InvalidOperationException("Queue is empty");
|
||||
|
||||
var item = _queue.Dequeue();
|
||||
|
||||
if (_queue.Count == 0)
|
||||
_dataAvailableEvent.Reset();
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tries to remove and return the item at the beginning of the queue</summary>
|
||||
/// <param name="item">When successful, contains the dequeued item</param>
|
||||
/// <returns>True if an item was successfully dequeued</returns>
|
||||
public bool TryDequeue(out T item)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
item = default(T)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
{
|
||||
item = default(T)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = _queue.Dequeue();
|
||||
|
||||
if (_queue.Count == 0)
|
||||
_dataAvailableEvent.Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the item at the beginning of the queue without removing it</summary>
|
||||
/// <returns>The item at the beginning of the queue</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the queue is empty</exception>
|
||||
public T Peek()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(CrestronQueue<T>));
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
throw new InvalidOperationException("Queue is empty");
|
||||
|
||||
return _queue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tries to return the item at the beginning of the queue without removing it</summary>
|
||||
/// <param name="item">When successful, contains the item at the beginning of the queue</param>
|
||||
/// <returns>True if an item was found</returns>
|
||||
public bool TryPeek(out T item)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
item = default(T)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
{
|
||||
item = default(T)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
item = _queue.Peek();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the queue</summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_queue.Clear();
|
||||
_dataAvailableEvent.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Waits for data to become available in the queue</summary>
|
||||
/// <param name="timeout">Timeout in milliseconds</param>
|
||||
/// <returns>True if data became available within the timeout</returns>
|
||||
public bool WaitForData(int timeout)
|
||||
{
|
||||
if (_disposed) return false;
|
||||
|
||||
return _dataAvailableEvent.Wait(timeout);
|
||||
}
|
||||
|
||||
/// <summary>Waits for data to become available in the queue</summary>
|
||||
/// <returns>True when data becomes available</returns>
|
||||
public bool WaitForData()
|
||||
{
|
||||
if (_disposed) return false;
|
||||
|
||||
_dataAvailableEvent.Wait();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Copies the queue elements to an array</summary>
|
||||
/// <returns>Array containing the queue elements</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
if (_disposed) return new T[0];
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _queue.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>Disposes the queue and releases resources</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Protected dispose method</summary>
|
||||
/// <param name="disposing">True if disposing managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_dataAvailableEvent?.Dispose();
|
||||
Clear();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
131
src/CrestronMock/EventTypes.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>Mock eProgramStatusEventType enumeration</summary>
|
||||
public enum eProgramStatusEventType
|
||||
{
|
||||
/// <summary>Program stopping</summary>
|
||||
Stopping = 0,
|
||||
/// <summary>Program started</summary>
|
||||
Starting = 1,
|
||||
/// <summary>Program running</summary>
|
||||
Running = 2,
|
||||
/// <summary>Program paused</summary>
|
||||
Paused = 3
|
||||
}
|
||||
|
||||
/// <summary>Mock EthernetEventArgs class</summary>
|
||||
public class EthernetEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Gets the Ethernet adapter that triggered the event</summary>
|
||||
public int EthernetAdapter { get; private set; }
|
||||
|
||||
/// <summary>Gets the link status</summary>
|
||||
public bool LinkUp { get; private set; }
|
||||
|
||||
/// <summary>Gets the speed in Mbps</summary>
|
||||
public int Speed { get; private set; }
|
||||
|
||||
/// <summary>Gets whether it's full duplex</summary>
|
||||
public bool FullDuplex { get; private set; }
|
||||
|
||||
/// <summary>Gets the ethernet event type</summary>
|
||||
public eEthernetEventType EthernetEventType { get; private set; }
|
||||
|
||||
/// <summary>Initializes a new instance of EthernetEventArgs</summary>
|
||||
/// <param name="adapter">Ethernet adapter number</param>
|
||||
/// <param name="linkUp">Link status</param>
|
||||
/// <param name="speed">Speed in Mbps</param>
|
||||
/// <param name="fullDuplex">Full duplex status</param>
|
||||
public EthernetEventArgs(int adapter, bool linkUp, int speed, bool fullDuplex)
|
||||
{
|
||||
EthernetAdapter = adapter;
|
||||
LinkUp = linkUp;
|
||||
Speed = speed;
|
||||
FullDuplex = fullDuplex;
|
||||
EthernetEventType = linkUp ? eEthernetEventType.LinkUp : eEthernetEventType.LinkDown;
|
||||
}
|
||||
|
||||
/// <summary>Default constructor</summary>
|
||||
public EthernetEventArgs() : this(0, false, 0, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronIO
|
||||
{
|
||||
/// <summary>Mock FileInfo class for basic file operations</summary>
|
||||
public class FileInfo
|
||||
{
|
||||
/// <summary>Gets the full path of the file</summary>
|
||||
public string FullName { get; private set; }
|
||||
|
||||
/// <summary>Gets the name of the file</summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>Gets the directory name</summary>
|
||||
public string? DirectoryName { get; private set; }
|
||||
|
||||
/// <summary>Gets whether the file exists</summary>
|
||||
public bool Exists { get; private set; }
|
||||
|
||||
/// <summary>Gets the length of the file in bytes</summary>
|
||||
public long Length { get; private set; }
|
||||
|
||||
/// <summary>Gets the creation time</summary>
|
||||
public DateTime CreationTime { get; private set; }
|
||||
|
||||
/// <summary>Gets the last write time</summary>
|
||||
public DateTime LastWriteTime { get; private set; }
|
||||
|
||||
/// <summary>Gets the last access time</summary>
|
||||
public DateTime LastAccessTime { get; private set; }
|
||||
|
||||
/// <summary>Initializes a new instance of FileInfo</summary>
|
||||
/// <param name="fileName">Path to the file</param>
|
||||
public FileInfo(string fileName)
|
||||
{
|
||||
FullName = fileName ?? string.Empty;
|
||||
Name = System.IO.Path.GetFileName(fileName) ?? string.Empty;
|
||||
DirectoryName = System.IO.Path.GetDirectoryName(fileName);
|
||||
|
||||
// Mock file properties
|
||||
Exists = !string.IsNullOrEmpty(fileName);
|
||||
Length = 0;
|
||||
CreationTime = DateTime.Now;
|
||||
LastWriteTime = DateTime.Now;
|
||||
LastAccessTime = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>Deletes the file</summary>
|
||||
public void Delete()
|
||||
{
|
||||
// Mock implementation - just mark as not existing
|
||||
Exists = false;
|
||||
}
|
||||
|
||||
/// <summary>Creates a text file or opens an existing one for writing</summary>
|
||||
/// <returns>A mock StreamWriter</returns>
|
||||
public System.IO.StreamWriter CreateText()
|
||||
{
|
||||
var stream = new System.IO.MemoryStream();
|
||||
return new System.IO.StreamWriter(stream);
|
||||
}
|
||||
|
||||
/// <summary>Opens an existing file for reading</summary>
|
||||
/// <returns>A mock FileStream</returns>
|
||||
public System.IO.FileStream OpenRead()
|
||||
{
|
||||
// Mock implementation - return a memory stream wrapped as FileStream
|
||||
return new System.IO.FileStream(FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler delegates
|
||||
/// <summary>Ethernet event handler delegate</summary>
|
||||
public delegate void EthernetEventHandler(EthernetEventArgs args);
|
||||
}
|
||||
|
||||
|
||||
112
src/CrestronMock/Extensions.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threa case ETHERNET_PARAMETER_TO_GET.ETHERNET_HOSTNAME:
|
||||
return "mock-hostname";
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_MAC_ADDRESS:
|
||||
return "00:11:22:33:44:55";
|
||||
case ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME:
|
||||
return "mock-domain.local";
|
||||
default:
|
||||
return string.Empty; asks;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
public static class CrestronInvoke
|
||||
{
|
||||
public static void BeginInvoke(Func<object> func, object? state = null)
|
||||
{
|
||||
Task.Run(func);
|
||||
}
|
||||
|
||||
public static void BeginInvoke(Action action)
|
||||
{
|
||||
Task.Run(action);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CrestronEthernetHelper
|
||||
{
|
||||
/// <summary>Ethernet parameter enumeration</summary>
|
||||
public enum ETHERNET_PARAMETER_TO_GET
|
||||
{
|
||||
ETHERNET_HOSTNAME = 0,
|
||||
ETHERNET_DOMAIN_NAME = 1,
|
||||
ETHERNET_IP_ADDRESS = 2,
|
||||
ETHERNET_SUBNET_MASK = 3,
|
||||
ETHERNET_GATEWAY = 4,
|
||||
ETHERNET_DNS_SERVER = 5,
|
||||
ETHERNET_MAC_ADDRESS = 6,
|
||||
ETHERNET_DHCP_STATUS = 7,
|
||||
GET_CURRENT_DHCP_STATE = 8,
|
||||
GET_CURRENT_IP_ADDRESS = 9,
|
||||
GET_CURRENT_IP_MASK = 10,
|
||||
GET_CURRENT_ROUTER = 11,
|
||||
GET_HOSTNAME = 12,
|
||||
GET_LINK_STATUS = 13,
|
||||
GET_DOMAIN_NAME = 14
|
||||
}
|
||||
|
||||
public static List<string> GetEthernetAdaptersInfo()
|
||||
{
|
||||
return new List<string> { "MockAdapter" };
|
||||
}
|
||||
|
||||
public static string GetEthernetParameter(string adapter, string parameter)
|
||||
{
|
||||
return "MockValue";
|
||||
}
|
||||
|
||||
/// <summary>Get ethernet parameter as string</summary>
|
||||
/// <param name="parameter">The parameter to get</param>
|
||||
/// <param name="adapterType">The adapter type</param>
|
||||
/// <returns>The parameter value as string</returns>
|
||||
public static string GetEthernetParameter(ETHERNET_PARAMETER_TO_GET parameter, EthernetAdapterType adapterType)
|
||||
{
|
||||
// Mock implementation
|
||||
switch (parameter)
|
||||
{
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_IP_ADDRESS:
|
||||
return "192.168.1.100";
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_SUBNET_MASK:
|
||||
return "255.255.255.0";
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_GATEWAY:
|
||||
return "192.168.1.1";
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_HOSTNAME:
|
||||
return "MockHost";
|
||||
case ETHERNET_PARAMETER_TO_GET.ETHERNET_MAC_ADDRESS:
|
||||
return "00:11:22:33:44:55";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get adapter ID for specified adapter type</summary>
|
||||
/// <param name="adapterType">The adapter type</param>
|
||||
/// <returns>The adapter ID</returns>
|
||||
public static int GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType adapterType)
|
||||
{
|
||||
// Mock implementation
|
||||
return (int)adapterType;
|
||||
}
|
||||
|
||||
/// <summary>Check if control subnet is in automatic mode</summary>
|
||||
/// <param name="adapterId">The adapter ID</param>
|
||||
/// <returns>True if in automatic mode</returns>
|
||||
public static bool IsControlSubnetInAutomaticMode(int adapterId)
|
||||
{
|
||||
// Mock implementation
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock EthernetAdapterType enumeration</summary>
|
||||
public enum EthernetAdapterType
|
||||
{
|
||||
/// <summary>Ethernet LAN adapter</summary>
|
||||
EthernetLANAdapter = 0,
|
||||
/// <summary>Control subnet adapter</summary>
|
||||
ControlSubnet = 1,
|
||||
/// <summary>Auto-detect adapter</summary>
|
||||
EthernetAdapterAuto = 2
|
||||
}
|
||||
}
|
||||
407
src/CrestronMock/HttpCwsServer.cs
Normal file
@@ -0,0 +1,407 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Crestron.SimplSharp.WebScripting
|
||||
{
|
||||
/// <summary>Mock HttpCwsServer class for HTTP web server functionality</summary>
|
||||
public class HttpCwsServer : IDisposable
|
||||
{
|
||||
private HttpListener? _httpListener;
|
||||
private bool _listening;
|
||||
private readonly Dictionary<string, IHttpCwsHandler> _routes = new Dictionary<string, IHttpCwsHandler>();
|
||||
|
||||
/// <summary>Gets or sets the port number</summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>Gets whether the server is listening</summary>
|
||||
public bool Listening => _listening;
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsServer</summary>
|
||||
public HttpCwsServer()
|
||||
{
|
||||
Port = 80;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsServer</summary>
|
||||
/// <param name="port">Port number to listen on</param>
|
||||
public HttpCwsServer(int port)
|
||||
{
|
||||
Port = port;
|
||||
}
|
||||
|
||||
/// <summary>Starts the HTTP server</summary>
|
||||
/// <returns>True if started successfully</returns>
|
||||
public bool Start()
|
||||
{
|
||||
if (_listening)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
_httpListener = new HttpListener();
|
||||
_httpListener.Prefixes.Add($"http://+:{Port}/");
|
||||
_httpListener.Start();
|
||||
_listening = true;
|
||||
|
||||
_ = Task.Run(ProcessRequestsAsync);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stops the HTTP server</summary>
|
||||
/// <returns>True if stopped successfully</returns>
|
||||
public bool Stop()
|
||||
{
|
||||
if (!_listening)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
_listening = false;
|
||||
_httpListener?.Stop();
|
||||
_httpListener?.Close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds a route handler</summary>
|
||||
/// <param name="route">Route path</param>
|
||||
/// <param name="handler">Handler for the route</param>
|
||||
public void AddRoute(string route, IHttpCwsHandler handler)
|
||||
{
|
||||
_routes[route.ToLowerInvariant()] = handler;
|
||||
}
|
||||
|
||||
/// <summary>Removes a route handler</summary>
|
||||
/// <param name="route">Route path to remove</param>
|
||||
public void RemoveRoute(string route)
|
||||
{
|
||||
_routes.Remove(route.ToLowerInvariant());
|
||||
}
|
||||
|
||||
/// <summary>Unregisters a route handler</summary>
|
||||
/// <param name="route">Route path to unregister</param>
|
||||
public void Unregister(string route)
|
||||
{
|
||||
RemoveRoute(route);
|
||||
}
|
||||
|
||||
private async Task ProcessRequestsAsync()
|
||||
{
|
||||
while (_listening && _httpListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = await _httpListener.GetContextAsync();
|
||||
_ = Task.Run(() => HandleRequest(context));
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
// Listener was stopped
|
||||
break;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Listener was disposed
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle other exceptions
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRequest(HttpListenerContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = context.Request;
|
||||
var response = context.Response;
|
||||
|
||||
var path = request.Url?.AbsolutePath?.ToLowerInvariant() ?? "/";
|
||||
|
||||
var cwsContext = new HttpCwsContext(context);
|
||||
|
||||
if (_routes.TryGetValue(path, out var handler))
|
||||
{
|
||||
handler.ProcessRequest(cwsContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default 404 response
|
||||
response.StatusCode = 404;
|
||||
var buffer = Encoding.UTF8.GetBytes("Not Found");
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
response.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle request processing errors
|
||||
try
|
||||
{
|
||||
context.Response.StatusCode = 500;
|
||||
context.Response.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors when closing response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disposes the HttpCwsServer</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_httpListener?.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HttpCwsContext class representing an HTTP request/response context</summary>
|
||||
public class HttpCwsContext
|
||||
{
|
||||
private readonly HttpListenerContext _context;
|
||||
|
||||
/// <summary>Gets the HTTP request</summary>
|
||||
public HttpCwsRequest Request { get; }
|
||||
|
||||
/// <summary>Gets the HTTP response</summary>
|
||||
public HttpCwsResponse Response { get; }
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsContext</summary>
|
||||
/// <param name="context">Underlying HttpListenerContext</param>
|
||||
public HttpCwsContext(HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
Request = new HttpCwsRequest(context.Request);
|
||||
Response = new HttpCwsResponse(context.Response);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HttpCwsRequest class representing an HTTP request</summary>
|
||||
public class HttpCwsRequest
|
||||
{
|
||||
private readonly HttpListenerRequest _request;
|
||||
|
||||
/// <summary>Gets the HTTP method</summary>
|
||||
public string HttpMethod => _request.HttpMethod;
|
||||
|
||||
/// <summary>Gets the request URL</summary>
|
||||
public Uri? Url => _request.Url;
|
||||
|
||||
/// <summary>Gets the request headers</summary>
|
||||
public System.Collections.Specialized.NameValueCollection Headers => _request.Headers;
|
||||
|
||||
/// <summary>Gets the query string</summary>
|
||||
public System.Collections.Specialized.NameValueCollection QueryString => _request.QueryString;
|
||||
|
||||
/// <summary>Gets the content type</summary>
|
||||
public string? ContentType => _request.ContentType;
|
||||
|
||||
/// <summary>Gets the content length</summary>
|
||||
public long ContentLength => _request.ContentLength64;
|
||||
|
||||
/// <summary>Gets the input stream</summary>
|
||||
public Stream InputStream => _request.InputStream;
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsRequest</summary>
|
||||
/// <param name="request">Underlying HttpListenerRequest</param>
|
||||
public HttpCwsRequest(HttpListenerRequest request)
|
||||
{
|
||||
_request = request;
|
||||
}
|
||||
|
||||
/// <summary>Gets the request body as a string</summary>
|
||||
/// <returns>Request body content</returns>
|
||||
public string GetRequestBodyAsString()
|
||||
{
|
||||
using var reader = new StreamReader(InputStream, Encoding.UTF8);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HttpCwsResponse class representing an HTTP response</summary>
|
||||
public class HttpCwsResponse
|
||||
{
|
||||
private readonly HttpListenerResponse _response;
|
||||
|
||||
/// <summary>Gets or sets the status code</summary>
|
||||
public int StatusCode
|
||||
{
|
||||
get => _response.StatusCode;
|
||||
set => _response.StatusCode = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the status description</summary>
|
||||
public string StatusDescription
|
||||
{
|
||||
get => _response.StatusDescription;
|
||||
set => _response.StatusDescription = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the content type</summary>
|
||||
public string? ContentType
|
||||
{
|
||||
get => _response.ContentType;
|
||||
set => _response.ContentType = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the content length</summary>
|
||||
public long ContentLength
|
||||
{
|
||||
get => _response.ContentLength64;
|
||||
set => _response.ContentLength64 = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets the response headers</summary>
|
||||
public WebHeaderCollection Headers => _response.Headers;
|
||||
|
||||
/// <summary>Gets the output stream</summary>
|
||||
public Stream OutputStream => _response.OutputStream;
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsResponse</summary>
|
||||
/// <param name="response">Underlying HttpListenerResponse</param>
|
||||
public HttpCwsResponse(HttpListenerResponse response)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
/// <summary>Writes a string to the response</summary>
|
||||
/// <param name="content">Content to write</param>
|
||||
public void Write(string content)
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes(content);
|
||||
ContentLength = buffer.Length;
|
||||
OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>Writes bytes to the response</summary>
|
||||
/// <param name="buffer">Buffer to write</param>
|
||||
/// <param name="offset">Offset in buffer</param>
|
||||
/// <param name="count">Number of bytes to write</param>
|
||||
public void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
OutputStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>Ends the response</summary>
|
||||
public void End()
|
||||
{
|
||||
try
|
||||
{
|
||||
_response.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore exceptions during close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Interface for HTTP request handlers</summary>
|
||||
public interface IHttpCwsHandler
|
||||
{
|
||||
/// <summary>Processes an HTTP request</summary>
|
||||
/// <param name="context">HTTP context</param>
|
||||
void ProcessRequest(HttpCwsContext context);
|
||||
}
|
||||
|
||||
/// <summary>Mock HttpCwsRoute class for route management</summary>
|
||||
public class HttpCwsRoute
|
||||
{
|
||||
/// <summary>Gets or sets the route path</summary>
|
||||
public string Path { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the HTTP method</summary>
|
||||
public string Method { get; set; } = "GET";
|
||||
|
||||
/// <summary>Gets or sets the route handler</summary>
|
||||
public IHttpCwsHandler? Handler { get; set; }
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsRoute</summary>
|
||||
public HttpCwsRoute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsRoute</summary>
|
||||
/// <param name="path">Route path</param>
|
||||
/// <param name="handler">Route handler</param>
|
||||
public HttpCwsRoute(string path, IHttpCwsHandler handler)
|
||||
{
|
||||
Path = path;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpCwsRoute</summary>
|
||||
/// <param name="path">Route path</param>
|
||||
/// <param name="method">HTTP method</param>
|
||||
/// <param name="handler">Route handler</param>
|
||||
public HttpCwsRoute(string path, string method, IHttpCwsHandler handler)
|
||||
{
|
||||
Path = path;
|
||||
Method = method;
|
||||
Handler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP CWS route collection</summary>
|
||||
public class HttpCwsRouteCollection
|
||||
{
|
||||
private readonly List<HttpCwsRoute> _routes = new List<HttpCwsRoute>();
|
||||
|
||||
/// <summary>Adds a route</summary>
|
||||
/// <param name="route">Route to add</param>
|
||||
public void Add(HttpCwsRoute route)
|
||||
{
|
||||
_routes.Add(route);
|
||||
}
|
||||
|
||||
/// <summary>Removes a route</summary>
|
||||
/// <param name="route">Route to remove</param>
|
||||
public void Remove(HttpCwsRoute route)
|
||||
{
|
||||
_routes.Remove(route);
|
||||
}
|
||||
|
||||
/// <summary>Clears all routes</summary>
|
||||
public void Clear()
|
||||
{
|
||||
_routes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Gets route count</summary>
|
||||
public int Count => _routes.Count;
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP CWS request event args</summary>
|
||||
public class HttpCwsRequestEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Gets the HTTP context</summary>
|
||||
public HttpCwsContext Context { get; private set; }
|
||||
|
||||
/// <summary>Initializes new instance</summary>
|
||||
/// <param name="context">HTTP context</param>
|
||||
public HttpCwsRequestEventArgs(HttpCwsContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/CrestronMock/NetworkingExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
// Additional types needed for networking compatibility
|
||||
|
||||
/// <summary>IP address extensions and utilities</summary>
|
||||
public static class IPAddress
|
||||
{
|
||||
/// <summary>Parse IP address string</summary>
|
||||
public static System.Net.IPAddress Parse(string ipString)
|
||||
{
|
||||
return System.Net.IPAddress.Parse(ipString);
|
||||
}
|
||||
|
||||
/// <summary>Any IP address</summary>
|
||||
public static System.Net.IPAddress Any => System.Net.IPAddress.Any;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
/// <summary>Extensions for CrestronQueue</summary>
|
||||
public static class CrestronQueueExtensions
|
||||
{
|
||||
/// <summary>Try to enqueue item</summary>
|
||||
public static bool TryToEnqueue<T>(this CrestronQueue<T> queue, T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/CrestronMock/SecureTCPClient.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
/// <summary>Mock implementation of Crestron SecureTCPClient for testing purposes</summary>
|
||||
public class SecureTCPClient : TCPClient
|
||||
{
|
||||
/// <summary>Initializes a new instance of the SecureTCPClient class</summary>
|
||||
/// <param name="addressToConnectTo">IP address to connect to</param>
|
||||
/// <param name="portNumber">Port number to connect to</param>
|
||||
/// <param name="bufferSize">Size of the receive buffer</param>
|
||||
public SecureTCPClient(string addressToConnectTo, int portNumber, int bufferSize)
|
||||
: base(addressToConnectTo, portNumber, bufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets whether to verify the host certificate</summary>
|
||||
public bool HostVerification { get; set; } = true;
|
||||
|
||||
/// <summary>Gets or sets whether to verify the peer certificate</summary>
|
||||
public bool PeerVerification { get; set; } = true;
|
||||
|
||||
/// <summary>Resets the client connection</summary>
|
||||
/// <param name="connectionFlag">Connection flag</param>
|
||||
public void Reset(int connectionFlag)
|
||||
{
|
||||
// Mock implementation
|
||||
DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/CrestronMock/SocketTypes.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
/// <summary>Mock EthernetAdapterType enumeration</summary>
|
||||
public enum EthernetAdapterType
|
||||
{
|
||||
/// <summary>Ethernet adapter 1</summary>
|
||||
EthernetLANAdapter = 0,
|
||||
/// <summary>Ethernet adapter 2</summary>
|
||||
EthernetAdapter2 = 1,
|
||||
/// <summary>Auto-detect adapter</summary>
|
||||
EthernetAdapterAuto = 2
|
||||
}
|
||||
|
||||
/// <summary>Mock SocketErrorCodes enumeration</summary>
|
||||
public enum SocketErrorCodes
|
||||
{
|
||||
/// <summary>Operation completed successfully</summary>
|
||||
SOCKET_OK = 0,
|
||||
/// <summary>Socket operation pending</summary>
|
||||
SOCKET_OPERATION_PENDING = 1,
|
||||
/// <summary>Socket not connected</summary>
|
||||
SOCKET_NOT_CONNECTED = 2,
|
||||
/// <summary>Connection failed</summary>
|
||||
SOCKET_CONNECTION_FAILED = 3,
|
||||
/// <summary>Invalid client index</summary>
|
||||
SOCKET_INVALID_CLIENT_INDEX = 4,
|
||||
/// <summary>DNS lookup failed</summary>
|
||||
SOCKET_DNS_LOOKUP_FAILED = 5,
|
||||
/// <summary>Invalid address</summary>
|
||||
SOCKET_INVALID_ADDRESS = 6,
|
||||
/// <summary>Connection timed out</summary>
|
||||
SOCKET_CONNECTION_TIMEOUT = 7,
|
||||
/// <summary>Send data failed</summary>
|
||||
SOCKET_SEND_DATA_FAILED = 8,
|
||||
/// <summary>Receive data failed</summary>
|
||||
SOCKET_RECEIVE_DATA_FAILED = 9,
|
||||
/// <summary>Socket closed</summary>
|
||||
SOCKET_CLOSED = 10,
|
||||
/// <summary>Socket disconnected</summary>
|
||||
SOCKET_DISCONNECTED = 11,
|
||||
/// <summary>Max connections reached</summary>
|
||||
SOCKET_MAX_CONNECTIONS_REACHED = 12,
|
||||
/// <summary>Permission denied</summary>
|
||||
SOCKET_PERMISSION_DENIED = 13,
|
||||
/// <summary>Address already in use</summary>
|
||||
SOCKET_ADDRESS_IN_USE = 14,
|
||||
/// <summary>Invalid parameter</summary>
|
||||
SOCKET_INVALID_PARAMETER = 15,
|
||||
/// <summary>Connection in progress</summary>
|
||||
SOCKET_CONNECTION_IN_PROGRESS = 16
|
||||
}
|
||||
|
||||
/// <summary>Mock socket exception</summary>
|
||||
public class SocketException : Exception
|
||||
{
|
||||
/// <summary>Error code</summary>
|
||||
public int ErrorCode { get; }
|
||||
|
||||
/// <summary>Constructor with error code</summary>
|
||||
public SocketException(int errorCode, string message) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>Constructor with message only</summary>
|
||||
public SocketException(string message) : base(message)
|
||||
{
|
||||
ErrorCode = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/CrestronMock/SystemTypes.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Crestron.SimplSharp
|
||||
{
|
||||
public delegate void ProgramStatusEventHandler(eProgramStatusEventType eventType);
|
||||
|
||||
public class InitialParametersClass
|
||||
{
|
||||
public static string ApplicationDirectory { get; set; } = "/User/";
|
||||
public static string ProgramIDTag { get; set; } = "MockProgram";
|
||||
public static string ApplicationName { get; set; } = "MockApplication";
|
||||
public static string FirmwareVersion { get; set; } = "1.0.0.0";
|
||||
public static uint ProgramNumber { get; set; } = 1;
|
||||
public static eDevicePlatform DevicePlatform { get; set; } = eDevicePlatform.Appliance;
|
||||
public static eCrestronSeries ControllerSeries { get; set; } = eCrestronSeries.FourSeries;
|
||||
|
||||
// Additional properties needed by PepperDash.Core
|
||||
public static string RoomId { get; set; } = "Room001";
|
||||
public static string RoomName { get; set; } = "Conference Room";
|
||||
public static uint ApplicationNumber { get; set; } = 1;
|
||||
public static string ControllerPromptName { get; set; } = "TestController";
|
||||
public static string ProgramDirectory { get; set; } = "/User/";
|
||||
}
|
||||
|
||||
public enum eDevicePlatform
|
||||
{
|
||||
Appliance = 0,
|
||||
Server = 1,
|
||||
ControlSystem = 2
|
||||
}
|
||||
|
||||
public enum eCrestronSeries
|
||||
{
|
||||
TwoSeries = 2,
|
||||
ThreeSeries = 3,
|
||||
FourSeries = 4,
|
||||
// Alias names used in some contexts
|
||||
Series2 = 2,
|
||||
Series3 = 3,
|
||||
Series4 = 4
|
||||
}
|
||||
|
||||
public enum eRuntimeEnvironment
|
||||
{
|
||||
SimplSharpPro = 0,
|
||||
SimplSharp = 1
|
||||
}
|
||||
|
||||
public enum eProgramCompatibility
|
||||
{
|
||||
Series3And4 = 0,
|
||||
Series3Only = 1,
|
||||
Series4Only = 2
|
||||
}
|
||||
|
||||
public static class Timeout
|
||||
{
|
||||
public const int Infinite = -1;
|
||||
}
|
||||
}
|
||||
360
src/CrestronMock/TCPClient.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
/// <summary>Mock SocketStatus enumeration</summary>
|
||||
public enum SocketStatus
|
||||
{
|
||||
/// <summary>Socket is connecting</summary>
|
||||
SOCKET_STATUS_WAITING = 1,
|
||||
/// <summary>Socket is connected</summary>
|
||||
SOCKET_STATUS_CONNECTED = 2,
|
||||
/// <summary>Socket is not connected</summary>
|
||||
SOCKET_STATUS_NOT_CONNECTED = 3,
|
||||
/// <summary>Connection broken</summary>
|
||||
SOCKET_STATUS_BROKEN_REMOTELY = 4,
|
||||
/// <summary>Connection broken locally</summary>
|
||||
SOCKET_STATUS_BROKEN_LOCALLY = 5,
|
||||
/// <summary>DNS resolution failed</summary>
|
||||
SOCKET_STATUS_DNS_RESOLUTION_FAILED = 6,
|
||||
/// <summary>Connection failed</summary>
|
||||
SOCKET_STATUS_CONNECT_FAILED = 7,
|
||||
/// <summary>Socket error</summary>
|
||||
SOCKET_STATUS_SOCKET_ERROR = 8,
|
||||
/// <summary>Secure connection failed</summary>
|
||||
SOCKET_STATUS_SSL_FAILED = 9,
|
||||
/// <summary>No connection available</summary>
|
||||
SOCKET_STATUS_NO_CONNECT = 10
|
||||
}
|
||||
|
||||
/// <summary>Mock ServerState enumeration</summary>
|
||||
public enum ServerState
|
||||
{
|
||||
/// <summary>Server is not listening</summary>
|
||||
SERVER_NOT_LISTENING = 0,
|
||||
/// <summary>Server is listening</summary>
|
||||
SERVER_LISTENING = 1,
|
||||
/// <summary>Server is connected</summary>
|
||||
SERVER_CONNECTED = 2
|
||||
}
|
||||
|
||||
/// <summary>Mock event handler for TCP client status changes</summary>
|
||||
/// <param name="client">The TCP client</param>
|
||||
/// <param name="clientSocketStatus">The socket status</param>
|
||||
public delegate void TCPClientSocketStatusChangeEventHandler(TCPClient client, SocketStatus clientSocketStatus);
|
||||
|
||||
/// <summary>Delegate for TCP client connect callback</summary>
|
||||
/// <param name="client">TCP client instance</param>
|
||||
public delegate void TCPClientConnectCallback(TCPClient client);
|
||||
|
||||
/// <summary>Delegate for TCP client send callback</summary>
|
||||
/// <param name="client">TCP client instance</param>
|
||||
/// <param name="numberOfBytesSent">Number of bytes sent</param>
|
||||
public delegate void TCPClientSendCallback(TCPClient client, int numberOfBytesSent);
|
||||
|
||||
/// <summary>Delegate for TCP client receive callback</summary>
|
||||
/// <param name="client">TCP client instance</param>
|
||||
/// <param name="numberOfBytesReceived">Number of bytes received</param>
|
||||
public delegate void TCPClientReceiveCallback(TCPClient client, int numberOfBytesReceived);
|
||||
|
||||
/// <summary>Mock event handler for receiving TCP client data</summary>
|
||||
/// <param name="client">The TCP client</param>
|
||||
/// <param name="numberOfBytesReceived">Number of bytes received</param>
|
||||
public delegate void TCPClientReceiveEventHandler(TCPClient client, int numberOfBytesReceived);
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of Crestron TCPClient for testing purposes
|
||||
/// Provides the same public API surface as the real TCPClient
|
||||
/// </summary>
|
||||
public class TCPClient : IDisposable
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>Event fired when socket status changes</summary>
|
||||
public event TCPClientSocketStatusChangeEventHandler? SocketStatusChange;
|
||||
|
||||
/// <summary>Event fired when data is received</summary>
|
||||
public event TCPClientReceiveEventHandler? DataReceived;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>Gets the client socket status</summary>
|
||||
public SocketStatus ClientStatus { get; private set; } = SocketStatus.SOCKET_STATUS_NOT_CONNECTED;
|
||||
|
||||
/// <summary>Gets or sets the address to connect to</summary>
|
||||
public string AddressToConnectTo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets or sets the port number to connect to</summary>
|
||||
public int PortNumber { get; set; } = 0;
|
||||
|
||||
/// <summary>Gets the number of bytes received in the incoming data buffer</summary>
|
||||
public int IncomingDataBufferSize { get; private set; } = 0;
|
||||
|
||||
/// <summary>Gets or sets the socket send timeout in milliseconds</summary>
|
||||
public int SocketSendTimeout { get; set; } = 30000;
|
||||
|
||||
/// <summary>Gets or sets the socket receive timeout in milliseconds</summary>
|
||||
public int SocketReceiveTimeout { get; set; } = 30000;
|
||||
|
||||
/// <summary>Gets or sets whether to keep the connection alive</summary>
|
||||
public bool KeepAlive { get; set; } = false;
|
||||
|
||||
/// <summary>Gets or sets whether Nagle algorithm is enabled</summary>
|
||||
public bool EnableNagle { get; set; } = true;
|
||||
|
||||
/// <summary>Gets the number of bytes available to read</summary>
|
||||
public int BytesAvailable => IncomingDataBufferSize;
|
||||
|
||||
/// <summary>Gets or sets the socket send or receive timeout in milliseconds</summary>
|
||||
public int SocketSendOrReceiveTimeOutInMs
|
||||
{
|
||||
get => SocketSendTimeout;
|
||||
set => SocketSendTimeout = SocketReceiveTimeout = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets the address the client is connected to</summary>
|
||||
public string AddressClientConnectedTo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the local port number of the client</summary>
|
||||
public uint LocalPortNumberOfClient { get; private set; } = 0;
|
||||
|
||||
/// <summary>Gets the incoming data buffer</summary>
|
||||
public byte[] IncomingDataBuffer { get; private set; } = new byte[0];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>Initializes a new instance of the TCPClient class</summary>
|
||||
/// <param name="addressToConnectTo">IP address to connect to</param>
|
||||
/// <param name="portNumber">Port number to connect to</param>
|
||||
/// <param name="bufferSize">Size of the receive buffer</param>
|
||||
public TCPClient(string addressToConnectTo, int portNumber, int bufferSize)
|
||||
{
|
||||
AddressToConnectTo = addressToConnectTo;
|
||||
PortNumber = portNumber;
|
||||
_bufferSize = bufferSize;
|
||||
_receiveBuffer = new byte[bufferSize];
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the TCPClient class</summary>
|
||||
/// <param name="addressToConnectTo">IP address to connect to</param>
|
||||
/// <param name="portNumber">Port number to connect to</param>
|
||||
/// <param name="bufferSize">Size of the receive buffer</param>
|
||||
/// <param name="ethernetAdapterToBindTo">Ethernet adapter to bind to</param>
|
||||
public TCPClient(string addressToConnectTo, int portNumber, int bufferSize, EthernetAdapterType ethernetAdapterToBindTo)
|
||||
{
|
||||
AddressToConnectTo = addressToConnectTo;
|
||||
PortNumber = portNumber;
|
||||
_bufferSize = bufferSize;
|
||||
_receiveBuffer = new byte[bufferSize];
|
||||
// Note: EthernetAdapterType is ignored in mock implementation
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly int _bufferSize;
|
||||
private readonly byte[] _receiveBuffer;
|
||||
private bool _disposed = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>Connects to the remote endpoint asynchronously</summary>
|
||||
/// <returns>Status of the connection attempt</returns>
|
||||
public SocketStatus ConnectToServerAsync()
|
||||
{
|
||||
if (_disposed) return SocketStatus.SOCKET_STATUS_SOCKET_ERROR;
|
||||
|
||||
// Mock connection - simulate successful connection
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||
AddressClientConnectedTo = AddressToConnectTo;
|
||||
SocketStatusChange?.Invoke(this, ClientStatus);
|
||||
return ClientStatus;
|
||||
}
|
||||
|
||||
/// <summary>Connects to the remote endpoint asynchronously with callback</summary>
|
||||
/// <param name="callback">Callback to invoke when connection completes</param>
|
||||
/// <returns>Status of the connection attempt</returns>
|
||||
public SocketStatus ConnectToServerAsync(TCPClientConnectCallback callback)
|
||||
{
|
||||
var status = ConnectToServerAsync();
|
||||
callback?.Invoke(this);
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>Connects to the remote endpoint</summary>
|
||||
/// <returns>Status of the connection attempt</returns>
|
||||
public SocketStatus ConnectToServer()
|
||||
{
|
||||
return ConnectToServerAsync();
|
||||
}
|
||||
|
||||
/// <summary>Disconnects from the remote endpoint</summary>
|
||||
/// <returns>Status of the disconnection</returns>
|
||||
public SocketStatus DisconnectFromServer()
|
||||
{
|
||||
if (_disposed) return SocketStatus.SOCKET_STATUS_SOCKET_ERROR;
|
||||
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_NOT_CONNECTED;
|
||||
SocketStatusChange?.Invoke(this, ClientStatus);
|
||||
return ClientStatus;
|
||||
}
|
||||
|
||||
/// <summary>Sends data to the connected server</summary>
|
||||
/// <param name="dataToSend">Data to send as a string</param>
|
||||
/// <returns>Number of bytes sent, or -1 on error</returns>
|
||||
public int SendData(string dataToSend)
|
||||
{
|
||||
if (_disposed || string.IsNullOrEmpty(dataToSend)) return -1;
|
||||
if (ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) return -1;
|
||||
|
||||
// Mock send - return the length of the string as bytes sent
|
||||
return System.Text.Encoding.UTF8.GetByteCount(dataToSend);
|
||||
}
|
||||
|
||||
/// <summary>Sends data to the connected server</summary>
|
||||
/// <param name="dataToSend">Data to send as byte array</param>
|
||||
/// <param name="lengthToSend">Number of bytes to send</param>
|
||||
/// <returns>Number of bytes sent, or -1 on error</returns>
|
||||
public int SendData(byte[] dataToSend, int lengthToSend)
|
||||
{
|
||||
if (_disposed || dataToSend == null || lengthToSend <= 0) return -1;
|
||||
if (ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) return -1;
|
||||
if (lengthToSend > dataToSend.Length) return -1;
|
||||
|
||||
// Mock send - return the requested length
|
||||
return lengthToSend;
|
||||
}
|
||||
|
||||
/// <summary>Receives data from the server</summary>
|
||||
/// <param name="buffer">Buffer to receive data into</param>
|
||||
/// <param name="bufferIndex">Starting index in the buffer</param>
|
||||
/// <param name="lengthToReceive">Maximum number of bytes to receive</param>
|
||||
/// <returns>Number of bytes received, or -1 on error</returns>
|
||||
public int ReceiveData(byte[] buffer, int bufferIndex, int lengthToReceive)
|
||||
{
|
||||
if (_disposed || buffer == null || bufferIndex < 0 || lengthToReceive <= 0) return -1;
|
||||
if (ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) return -1;
|
||||
if (bufferIndex + lengthToReceive > buffer.Length) return -1;
|
||||
|
||||
// Mock receive - simulate no data available for now
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Receives data from the server as a string</summary>
|
||||
/// <param name="numberOfBytesToReceive">Maximum number of bytes to receive</param>
|
||||
/// <returns>Received data as string, or empty string on error</returns>
|
||||
public string ReceiveData(int numberOfBytesToReceive)
|
||||
{
|
||||
if (_disposed || numberOfBytesToReceive <= 0) return string.Empty;
|
||||
if (ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) return string.Empty;
|
||||
|
||||
// Mock receive - return empty string (no data available)
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>Sends data to the connected server asynchronously</summary>
|
||||
/// <param name="dataToSend">Data to send as byte array</param>
|
||||
/// <param name="lengthToSend">Number of bytes to send</param>
|
||||
/// <returns>Number of bytes sent, or -1 on error</returns>
|
||||
public int SendDataAsync(byte[] dataToSend, int lengthToSend)
|
||||
{
|
||||
return SendData(dataToSend, lengthToSend);
|
||||
}
|
||||
|
||||
/// <summary>Sends data to the connected server asynchronously with callback</summary>
|
||||
/// <param name="dataToSend">Data to send as byte array</param>
|
||||
/// <param name="lengthToSend">Number of bytes to send</param>
|
||||
/// <param name="callback">Callback to invoke when send completes</param>
|
||||
/// <returns>Number of bytes sent, or -1 on error</returns>
|
||||
public int SendDataAsync(byte[] dataToSend, int lengthToSend, TCPClientSendCallback callback)
|
||||
{
|
||||
var result = SendData(dataToSend, lengthToSend);
|
||||
callback?.Invoke(this, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Receives data from the server asynchronously</summary>
|
||||
/// <returns>Number of bytes received, or -1 on error</returns>
|
||||
public int ReceiveDataAsync()
|
||||
{
|
||||
if (_disposed) return -1;
|
||||
if (ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) return -1;
|
||||
|
||||
// Mock receive - simulate no data available
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Receives data from the server asynchronously with callback</summary>
|
||||
/// <param name="callback">Callback to invoke when data is received</param>
|
||||
/// <returns>Number of bytes received, or -1 on error</returns>
|
||||
public int ReceiveDataAsync(TCPClientReceiveCallback callback)
|
||||
{
|
||||
var result = ReceiveDataAsync();
|
||||
callback?.Invoke(this, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Simulates receiving data (for testing purposes)</summary>
|
||||
/// <param name="data">Data to simulate receiving</param>
|
||||
public void SimulateDataReceived(string data)
|
||||
{
|
||||
if (_disposed || string.IsNullOrEmpty(data)) return;
|
||||
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
|
||||
var bytesToCopy = Math.Min(bytes.Length, _receiveBuffer.Length);
|
||||
Array.Copy(bytes, _receiveBuffer, bytesToCopy);
|
||||
IncomingDataBufferSize = bytesToCopy;
|
||||
|
||||
DataReceived?.Invoke(this, bytesToCopy);
|
||||
}
|
||||
|
||||
/// <summary>Simulates a socket status change (for testing purposes)</summary>
|
||||
/// <param name="newStatus">New socket status</param>
|
||||
public void SimulateStatusChange(SocketStatus newStatus)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
ClientStatus = newStatus;
|
||||
SocketStatusChange?.Invoke(this, newStatus);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>Disposes the TCP client and releases resources</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Protected dispose method</summary>
|
||||
/// <param name="disposing">True if disposing managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Disconnect if still connected
|
||||
if (ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
552
src/CrestronMock/TCPServer.cs
Normal file
@@ -0,0 +1,552 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
/// <summary>Mock TCPServer class for server-side TCP operations</summary>
|
||||
public class TCPServer : IDisposable
|
||||
{
|
||||
private TcpListener? _listener;
|
||||
private readonly List<TCPClientConnection> _clients = new List<TCPClientConnection>();
|
||||
private bool _listening;
|
||||
private readonly object _lockObject = new object();
|
||||
private int _bufferSize = 4096;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
/// <summary>Event fired when waiting for connections</summary>
|
||||
public event TCPServerWaitingForConnectionsEventHandler? WaitingForConnections;
|
||||
|
||||
/// <summary>Event fired when a client connects</summary>
|
||||
public event TCPServerClientConnectEventHandler? ClientConnected;
|
||||
|
||||
/// <summary>Event fired when a client disconnects</summary>
|
||||
public event TCPServerClientDisconnectEventHandler? ClientDisconnected;
|
||||
|
||||
/// <summary>Event fired when data is received from a client</summary>
|
||||
public event TCPServerReceiveDataEventHandler? ReceivedData;
|
||||
|
||||
/// <summary>Event fired when socket status changes</summary>
|
||||
public event TCPServerWaitingForConnectionsEventHandler? SocketStatusChange;
|
||||
|
||||
/// <summary>Gets the server state</summary>
|
||||
public ServerState State { get; private set; } = ServerState.SERVER_NOT_LISTENING;
|
||||
|
||||
/// <summary>Gets or sets the port number</summary>
|
||||
public int PortNumber { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the socket send or receive timeout in milliseconds</summary>
|
||||
public int SocketSendOrReceiveTimeOutInMs { get; set; } = 30000;
|
||||
|
||||
/// <summary>Gets the server socket status based on current state</summary>
|
||||
public SocketStatus ServerSocketStatus => State == ServerState.SERVER_LISTENING ? SocketStatus.SOCKET_STATUS_CONNECTED : SocketStatus.SOCKET_STATUS_NOT_CONNECTED; /// <summary>Gets the maximum number of clients</summary>
|
||||
public int MaxNumberOfClientSupported { get; private set; }
|
||||
|
||||
/// <summary>Gets the number of connected clients</summary>
|
||||
public int NumberOfClientsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _clients.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a TCP server with IP address binding</summary>
|
||||
/// <param name="ipAddress">IP address to bind to</param>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for incoming data</param>
|
||||
/// <param name="ethernetAdapterToBindTo">Ethernet adapter to bind to</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public TCPServer(string ipAddress, int portNumber, int bufferSize, EthernetAdapterType ethernetAdapterToBindTo, int maxNumberOfClientSupported)
|
||||
{
|
||||
PortNumber = portNumber;
|
||||
MaxNumberOfClientSupported = maxNumberOfClientSupported;
|
||||
_bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>Creates a TCP server</summary>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for incoming data</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public TCPServer(int portNumber, int bufferSize, int maxNumberOfClientSupported)
|
||||
{
|
||||
PortNumber = portNumber;
|
||||
MaxNumberOfClientSupported = maxNumberOfClientSupported;
|
||||
_bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>Creates a TCP server with just port and max clients</summary>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public TCPServer(int portNumber, int maxNumberOfClientSupported)
|
||||
{
|
||||
PortNumber = portNumber;
|
||||
MaxNumberOfClientSupported = maxNumberOfClientSupported;
|
||||
_bufferSize = 4096; // Default buffer size
|
||||
}
|
||||
|
||||
/// <summary>Starts listening for client connections</summary>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes WaitForConnectionAsync()
|
||||
{
|
||||
if (_listening)
|
||||
return SocketErrorCodes.SOCKET_OPERATION_PENDING;
|
||||
|
||||
try
|
||||
{
|
||||
_listener = new TcpListener(IPAddress.Any, PortNumber);
|
||||
_listener.Start();
|
||||
_listening = true;
|
||||
State = ServerState.SERVER_LISTENING;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// Start accepting clients in background
|
||||
_ = Task.Run(() => AcceptClientsAsync());
|
||||
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
State = ServerState.SERVER_NOT_LISTENING;
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Starts listening for connections asynchronously with callback</summary>
|
||||
/// <param name="ipAddress">IP address to listen on</param>
|
||||
/// <param name="callback">Callback for connection events</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes WaitForConnectionAsync(string ipAddress, TCPServerWaitingForConnectionsEventHandler callback)
|
||||
{
|
||||
SocketStatusChange += callback;
|
||||
return WaitForConnectionAsync();
|
||||
}
|
||||
|
||||
/// <summary>Stops listening for connections</summary>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes Stop()
|
||||
{
|
||||
if (!_listening)
|
||||
return SocketErrorCodes.SOCKET_NOT_CONNECTED;
|
||||
|
||||
try
|
||||
{
|
||||
_listening = false;
|
||||
_listener?.Stop();
|
||||
State = ServerState.SERVER_NOT_LISTENING;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (var client in _clients)
|
||||
{
|
||||
client.Disconnect();
|
||||
}
|
||||
_clients.Clear();
|
||||
}
|
||||
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sends data to a specific client</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data</param>
|
||||
/// <param name="clientIndex">Index of client to send to</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendData(byte[] data, int dataLength, uint clientIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (clientIndex >= _clients.Count)
|
||||
return SocketErrorCodes.SOCKET_INVALID_CLIENT_INDEX;
|
||||
|
||||
return _clients[(int)clientIndex].SendData(data, dataLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sends data to all connected clients</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendDataToAll(byte[] data, int dataLength)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
var result = SocketErrorCodes.SOCKET_OK;
|
||||
foreach (var client in _clients)
|
||||
{
|
||||
var sendResult = client.SendData(data, dataLength);
|
||||
if (sendResult != SocketErrorCodes.SOCKET_OK)
|
||||
result = sendResult;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disconnects a specific client</summary>
|
||||
/// <param name="clientIndex">Index of client to disconnect</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes Disconnect(uint clientIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (clientIndex >= _clients.Count)
|
||||
return SocketErrorCodes.SOCKET_INVALID_CLIENT_INDEX;
|
||||
|
||||
var client = _clients[(int)clientIndex];
|
||||
client.Disconnect();
|
||||
_clients.RemoveAt((int)clientIndex);
|
||||
|
||||
ClientDisconnected?.Invoke(this, new TCPServerClientDisconnectEventArgs((uint)clientIndex));
|
||||
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the IP address of a connected client</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>IP address as string</returns>
|
||||
public string GetAddressServerAcceptedConnectionFromForSpecificClient(uint clientIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (clientIndex >= _clients.Count)
|
||||
return string.Empty;
|
||||
|
||||
return _clients[(int)clientIndex].ClientIPAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the socket status for a specific client</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>Socket status</returns>
|
||||
public SocketStatus GetServerSocketStatusForSpecificClient(uint clientIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (clientIndex >= _clients.Count)
|
||||
return SocketStatus.SOCKET_STATUS_NOT_CONNECTED;
|
||||
|
||||
return _clients[(int)clientIndex].IsConnected ?
|
||||
SocketStatus.SOCKET_STATUS_CONNECTED :
|
||||
SocketStatus.SOCKET_STATUS_NOT_CONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the port number the server accepted connection from for a specific client</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>Port number</returns>
|
||||
public int GetPortNumberServerAcceptedConnectionFromForSpecificClient(uint clientIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (clientIndex >= _clients.Count)
|
||||
return 0;
|
||||
|
||||
return _clients[(int)clientIndex].ClientPort;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the local address the server accepted connection from for a specific client</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>Local address</returns>
|
||||
public string GetLocalAddressServerAcceptedConnectionFromForSpecificClient(uint clientIndex)
|
||||
{
|
||||
return "127.0.0.1"; // Mock local address
|
||||
}
|
||||
|
||||
/// <summary>Gets the incoming data buffer for a specific client</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>Incoming data buffer</returns>
|
||||
public byte[] GetIncomingDataBufferForSpecificClient(uint clientIndex)
|
||||
{
|
||||
return new byte[0]; // Mock empty buffer
|
||||
}
|
||||
|
||||
/// <summary>Sends data to a specific client asynchronously</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data to send</param>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendDataAsync(byte[] data, int dataLength, uint clientIndex)
|
||||
{
|
||||
return SendData(data, dataLength, clientIndex);
|
||||
}
|
||||
|
||||
/// <summary>Receives data from a specific client asynchronously</summary>
|
||||
/// <param name="clientIndex">Index of client</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes ReceiveDataAsync(uint clientIndex)
|
||||
{
|
||||
// Mock implementation - no data to receive
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
|
||||
private async Task AcceptClientsAsync()
|
||||
{
|
||||
while (_listening && _listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tcpClient = await _listener.AcceptTcpClientAsync();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_clients.Count >= MaxNumberOfClientSupported)
|
||||
{
|
||||
tcpClient.Close();
|
||||
continue;
|
||||
}
|
||||
|
||||
var clientConnection = new TCPClientConnection(tcpClient, (uint)_clients.Count);
|
||||
clientConnection.DataReceived += OnClientDataReceived;
|
||||
clientConnection.Disconnected += OnClientDisconnected;
|
||||
_clients.Add(clientConnection);
|
||||
|
||||
ClientConnected?.Invoke(this, new TCPServerClientConnectEventArgs((uint)(_clients.Count - 1)));
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Server was stopped
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle other exceptions
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientDataReceived(object? sender, TCPClientDataEventArgs e)
|
||||
{
|
||||
if (sender is TCPClientConnection client)
|
||||
{
|
||||
var args = new TCPServerReceiveDataEventArgs(e.Data, e.DataLength, client.ClientIndex);
|
||||
ReceivedData?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientDisconnected(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender is TCPClientConnection client)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
var index = _clients.IndexOf(client);
|
||||
if (index >= 0)
|
||||
{
|
||||
_clients.RemoveAt(index);
|
||||
ClientDisconnected?.Invoke(this, new TCPServerClientDisconnectEventArgs(client.ClientIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disposes the TCPServer</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_listener?.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock SecureTCPServer class for secure server-side TCP operations</summary>
|
||||
public class SecureTCPServer : TCPServer
|
||||
{
|
||||
/// <summary>Initializes a new instance of SecureTCPServer</summary>
|
||||
/// <param name="ipAddress">IP address to bind to</param>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for data reception</param>
|
||||
/// <param name="ethernetAdapterToBindTo">Ethernet adapter to bind to</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public SecureTCPServer(string ipAddress, int portNumber, int bufferSize, EthernetAdapterType ethernetAdapterToBindTo, int maxNumberOfClientSupported)
|
||||
: base(ipAddress, portNumber, bufferSize, ethernetAdapterToBindTo, maxNumberOfClientSupported)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of SecureTCPServer</summary>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for data reception</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public SecureTCPServer(int portNumber, int bufferSize, int maxNumberOfClientSupported)
|
||||
: base(portNumber, bufferSize, maxNumberOfClientSupported)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of SecureTCPServer</summary>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="maxNumberOfClientSupported">Maximum number of clients</param>
|
||||
public SecureTCPServer(int portNumber, int maxNumberOfClientSupported)
|
||||
: base(portNumber, 4096, maxNumberOfClientSupported) // Default buffer size
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the handshake timeout in seconds</summary>
|
||||
public int HandshakeTimeout { get; set; } = 30;
|
||||
|
||||
/// <summary>Event raised when socket status changes with client details</summary>
|
||||
public event SecureTCPServerSocketStatusEventHandler? SocketStatusChangeWithClientDetails;
|
||||
}
|
||||
|
||||
/// <summary>Internal class representing a client connection</summary>
|
||||
internal class TCPClientConnection
|
||||
{
|
||||
private readonly TcpClient _tcpClient;
|
||||
private readonly NetworkStream _stream;
|
||||
private readonly byte[] _buffer = new byte[4096];
|
||||
private bool _connected = true;
|
||||
|
||||
public uint ClientIndex { get; }
|
||||
public string ClientIPAddress { get; }
|
||||
public bool IsConnected => _connected;
|
||||
public int ClientPort { get; }
|
||||
|
||||
public event EventHandler<TCPClientDataEventArgs>? DataReceived;
|
||||
public event EventHandler? Disconnected;
|
||||
|
||||
public TCPClientConnection(TcpClient tcpClient, uint clientIndex)
|
||||
{
|
||||
_tcpClient = tcpClient;
|
||||
ClientIndex = clientIndex;
|
||||
_stream = tcpClient.GetStream();
|
||||
|
||||
var endpoint = tcpClient.Client.RemoteEndPoint as IPEndPoint;
|
||||
ClientIPAddress = endpoint?.Address.ToString() ?? "Unknown";
|
||||
ClientPort = endpoint?.Port ?? 0;
|
||||
|
||||
_ = Task.Run(ReceiveDataAsync);
|
||||
}
|
||||
|
||||
public SocketErrorCodes SendData(byte[] data, int dataLength)
|
||||
{
|
||||
if (!_connected)
|
||||
return SocketErrorCodes.SOCKET_NOT_CONNECTED;
|
||||
|
||||
try
|
||||
{
|
||||
_stream.Write(data, 0, dataLength);
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
if (!_connected)
|
||||
return;
|
||||
|
||||
_connected = false;
|
||||
_stream?.Close();
|
||||
_tcpClient?.Close();
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task ReceiveDataAsync()
|
||||
{
|
||||
while (_connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytesRead = await _stream.ReadAsync(_buffer, 0, _buffer.Length);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
var data = new byte[bytesRead];
|
||||
Array.Copy(_buffer, data, bytesRead);
|
||||
DataReceived?.Invoke(this, new TCPClientDataEventArgs(data, bytesRead));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Event args for TCP client data</summary>
|
||||
internal class TCPClientDataEventArgs : EventArgs
|
||||
{
|
||||
public byte[] Data { get; }
|
||||
public int DataLength { get; }
|
||||
|
||||
public TCPClientDataEventArgs(byte[] data, int dataLength)
|
||||
{
|
||||
Data = data;
|
||||
DataLength = dataLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Server state enumeration</summary>
|
||||
public enum SocketServerState
|
||||
{
|
||||
/// <summary>Server is not listening</summary>
|
||||
SERVER_NOT_LISTENING = 0,
|
||||
/// <summary>Server is listening for connections</summary>
|
||||
SERVER_LISTENING = 1,
|
||||
/// <summary>Server is connected</summary>
|
||||
SERVER_CONNECTED = 2
|
||||
}
|
||||
|
||||
// Event handler delegates
|
||||
public delegate void TCPServerWaitingForConnectionsEventHandler(TCPServer server, TCPServerWaitingForConnectionsEventArgs args);
|
||||
public delegate void TCPServerClientConnectEventHandler(TCPServer server, TCPServerClientConnectEventArgs args);
|
||||
public delegate void TCPServerClientDisconnectEventHandler(TCPServer server, TCPServerClientDisconnectEventArgs args);
|
||||
public delegate void TCPServerReceiveDataEventHandler(TCPServer server, TCPServerReceiveDataEventArgs args);
|
||||
public delegate void SecureTCPServerSocketStatusChangeEventHandler(SecureTCPServer server, TCPServerWaitingForConnectionsEventArgs args);
|
||||
/// <summary>Delegate for secure TCP server socket status changes with client details</summary>
|
||||
public delegate void SecureTCPServerSocketStatusEventHandler(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus);
|
||||
|
||||
// Event argument classes
|
||||
public class TCPServerWaitingForConnectionsEventArgs : EventArgs
|
||||
{
|
||||
public int ErrorCode { get; }
|
||||
public TCPServerWaitingForConnectionsEventArgs(int errorCode) { ErrorCode = errorCode; }
|
||||
}
|
||||
|
||||
public class TCPServerClientConnectEventArgs : EventArgs
|
||||
{
|
||||
public uint ClientIndex { get; }
|
||||
public TCPServerClientConnectEventArgs(uint clientIndex) { ClientIndex = clientIndex; }
|
||||
}
|
||||
|
||||
public class TCPServerClientDisconnectEventArgs : EventArgs
|
||||
{
|
||||
public uint ClientIndex { get; }
|
||||
public TCPServerClientDisconnectEventArgs(uint clientIndex) { ClientIndex = clientIndex; }
|
||||
}
|
||||
|
||||
public class TCPServerReceiveDataEventArgs : EventArgs
|
||||
{
|
||||
public byte[] Data { get; }
|
||||
public int DataLength { get; }
|
||||
public uint ClientIndex { get; }
|
||||
|
||||
public TCPServerReceiveDataEventArgs(byte[] data, int dataLength, uint clientIndex)
|
||||
{
|
||||
Data = data;
|
||||
DataLength = dataLength;
|
||||
ClientIndex = clientIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
270
src/CrestronMock/UDPServer.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Crestron.SimplSharp.CrestronSockets
|
||||
{
|
||||
/// <summary>Mock UDPServer class for UDP communication</summary>
|
||||
public class UDPServer : IDisposable
|
||||
{
|
||||
private UdpClient? _udpClient;
|
||||
private bool _listening;
|
||||
private readonly object _lockObject = new object();
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
/// <summary>Event fired when data is received</summary>
|
||||
public event UDPServerReceiveDataEventHandler? ReceivedData;
|
||||
|
||||
/// <summary>Gets the server state</summary>
|
||||
public SocketServerState State { get; private set; } = SocketServerState.SERVER_NOT_LISTENING;
|
||||
|
||||
/// <summary>Gets the server status (alias for State)</summary>
|
||||
public SocketServerState ServerStatus => State;
|
||||
|
||||
/// <summary>Gets the client status as SocketStatus</summary>
|
||||
public SocketStatus ClientStatus => State == SocketServerState.SERVER_LISTENING ? SocketStatus.SOCKET_STATUS_CONNECTED : SocketStatus.SOCKET_STATUS_NOT_CONNECTED;
|
||||
|
||||
/// <summary>Gets or sets the port number</summary>
|
||||
public int PortNumber { get; set; }
|
||||
|
||||
/// <summary>Gets the buffer size</summary>
|
||||
public int BufferSize { get; private set; }
|
||||
|
||||
/// <summary>Gets the IP address of the last message received from</summary>
|
||||
public string IPAddressLastMessageReceivedFrom { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the IP port of the last message received from</summary>
|
||||
public int IPPortLastMessageReceivedFrom { get; private set; }
|
||||
|
||||
/// <summary>Gets the incoming data buffer</summary>
|
||||
public byte[] IncomingDataBuffer { get; private set; } = new byte[0];
|
||||
|
||||
/// <summary>Initializes a new instance of UDPServer</summary>
|
||||
public UDPServer()
|
||||
{
|
||||
PortNumber = 0;
|
||||
BufferSize = 1024;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of UDPServer</summary>
|
||||
/// <param name="ipAddress">IP address to bind to</param>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for data reception</param>
|
||||
/// <param name="ethernetAdapterToBindTo">Ethernet adapter to bind to</param>
|
||||
public UDPServer(string ipAddress, int portNumber, int bufferSize, EthernetAdapterType ethernetAdapterToBindTo)
|
||||
{
|
||||
PortNumber = portNumber;
|
||||
BufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of UDPServer</summary>
|
||||
/// <param name="portNumber">Port number to listen on</param>
|
||||
/// <param name="bufferSize">Buffer size for data reception</param>
|
||||
public UDPServer(int portNumber, int bufferSize)
|
||||
{
|
||||
PortNumber = portNumber;
|
||||
BufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>Starts listening for UDP packets</summary>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes EnableUDPServer()
|
||||
{
|
||||
if (_listening)
|
||||
return SocketErrorCodes.SOCKET_OPERATION_PENDING;
|
||||
|
||||
try
|
||||
{
|
||||
_udpClient = new UdpClient(PortNumber);
|
||||
_listening = true;
|
||||
State = SocketServerState.SERVER_LISTENING;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_ = Task.Run(() => ReceiveDataAsync(_cancellationTokenSource.Token));
|
||||
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
State = SocketServerState.SERVER_NOT_LISTENING;
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Starts listening for UDP packets on specified hostname and port</summary>
|
||||
/// <param name="hostname">Hostname to bind to</param>
|
||||
/// <param name="port">Port number to listen on</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes EnableUDPServer(string hostname, int port)
|
||||
{
|
||||
PortNumber = port;
|
||||
return EnableUDPServer();
|
||||
}
|
||||
|
||||
/// <summary>Stops listening for UDP packets</summary>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes DisableUDPServer()
|
||||
{
|
||||
if (!_listening)
|
||||
return SocketErrorCodes.SOCKET_NOT_CONNECTED;
|
||||
|
||||
try
|
||||
{
|
||||
_listening = false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_udpClient?.Close();
|
||||
State = SocketServerState.SERVER_NOT_LISTENING;
|
||||
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sends data to a specific endpoint</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data</param>
|
||||
/// <param name="ipAddress">Target IP address</param>
|
||||
/// <param name="portNumber">Target port number</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendData(byte[] data, int dataLength, string ipAddress, int portNumber)
|
||||
{
|
||||
if (!_listening || _udpClient == null)
|
||||
return SocketErrorCodes.SOCKET_NOT_CONNECTED;
|
||||
|
||||
try
|
||||
{
|
||||
var endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), portNumber);
|
||||
_udpClient.Send(data, dataLength, endpoint);
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sends data to the last received endpoint</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendData(byte[] data, int dataLength)
|
||||
{
|
||||
return SendData(data, dataLength, IPAddressLastMessageReceivedFrom, IPPortLastMessageReceivedFrom);
|
||||
}
|
||||
|
||||
/// <summary>Receives data asynchronously</summary>
|
||||
/// <param name="callback">Callback to invoke when data is received</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes ReceiveDataAsync(UDPServerReceiveDataEventHandler callback)
|
||||
{
|
||||
ReceivedData += callback;
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
|
||||
/// <summary>Receives data asynchronously with simple callback</summary>
|
||||
/// <param name="callback">Simple callback to invoke when data is received</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes ReceiveDataAsync(UDPServerReceiveDataSimpleEventHandler callback)
|
||||
{
|
||||
// Convert simple callback to full event handler and subscribe
|
||||
ReceivedData += (server, args) => callback(server, args.DataLength);
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
|
||||
/// <summary>Sends data to a specific endpoint</summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <param name="dataLength">Length of data</param>
|
||||
/// <param name="endpoint">Target endpoint</param>
|
||||
/// <returns>SocketErrorCodes indicating success or failure</returns>
|
||||
public SocketErrorCodes SendData(byte[] data, int dataLength, IPEndPoint endpoint)
|
||||
{
|
||||
if (!_listening || _udpClient == null)
|
||||
return SocketErrorCodes.SOCKET_NOT_CONNECTED;
|
||||
|
||||
try
|
||||
{
|
||||
_udpClient.Send(data, dataLength, endpoint);
|
||||
return SocketErrorCodes.SOCKET_OK;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return SocketErrorCodes.SOCKET_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveDataAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (_listening && _udpClient != null && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpClient.ReceiveAsync();
|
||||
|
||||
var args = new UDPServerReceiveDataEventArgs(
|
||||
result.Buffer,
|
||||
result.Buffer.Length,
|
||||
result.RemoteEndPoint.Address.ToString(),
|
||||
result.RemoteEndPoint.Port);
|
||||
|
||||
ReceivedData?.Invoke(this, args);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// UDP client was disposed
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle other exceptions
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disposes the UDPServer</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
DisableUDPServer();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_udpClient?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler delegates for UDP
|
||||
public delegate void UDPServerReceiveDataEventHandler(UDPServer server, UDPServerReceiveDataEventArgs args);
|
||||
public delegate void UDPServerReceiveDataSimpleEventHandler(UDPServer server, int numBytes);
|
||||
|
||||
// Event argument classes for UDP
|
||||
public class UDPServerReceiveDataEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Gets the received data</summary>
|
||||
public byte[] Data { get; }
|
||||
|
||||
/// <summary>Gets the length of received data</summary>
|
||||
public int DataLength { get; }
|
||||
|
||||
/// <summary>Gets the sender's IP address</summary>
|
||||
public string IPAddress { get; }
|
||||
|
||||
/// <summary>Gets the sender's port number</summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>Initializes a new instance of UDPServerReceiveDataEventArgs</summary>
|
||||
/// <param name="data">Received data</param>
|
||||
/// <param name="dataLength">Length of received data</param>
|
||||
/// <param name="ipAddress">Sender's IP address</param>
|
||||
/// <param name="port">Sender's port number</param>
|
||||
public UDPServerReceiveDataEventArgs(byte[] data, int dataLength, string ipAddress, int port)
|
||||
{
|
||||
Data = data;
|
||||
DataLength = dataLength;
|
||||
IPAddress = ipAddress;
|
||||
Port = port;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/CrestronMock/UrlParserTypes.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Crestron.SimplSharp.Net.Http
|
||||
{
|
||||
/// <summary>Mock UrlParser for HTTP</summary>
|
||||
public static class UrlParser
|
||||
{
|
||||
/// <summary>Parse a URL string</summary>
|
||||
/// <param name="url">URL to parse</param>
|
||||
/// <returns>Parsed URL components</returns>
|
||||
public static UrlParserResult Parse(string url)
|
||||
{
|
||||
return new UrlParserResult { Url = url };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>URL parser result</summary>
|
||||
public class UrlParserResult
|
||||
{
|
||||
/// <summary>Original URL</summary>
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp.Net.Https
|
||||
{
|
||||
/// <summary>Mock UrlParser for HTTPS - different from HTTP version</summary>
|
||||
public static class UrlParser
|
||||
{
|
||||
/// <summary>Parse a URL string</summary>
|
||||
/// <param name="url">URL to parse</param>
|
||||
/// <returns>Parsed URL components</returns>
|
||||
public static UrlParserResult Parse(string url)
|
||||
{
|
||||
return new UrlParserResult { Url = url };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>HTTPS URL parser result</summary>
|
||||
public class UrlParserResult
|
||||
{
|
||||
/// <summary>Original URL</summary>
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
295
src/CrestronMock/WebAndNetworking.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Crestron.SimplSharp.Net.Http
|
||||
{
|
||||
/// <summary>HTTP request types</summary>
|
||||
public enum RequestType
|
||||
{
|
||||
/// <summary>GET request</summary>
|
||||
Get = 0,
|
||||
/// <summary>POST request</summary>
|
||||
Post = 1,
|
||||
/// <summary>PUT request</summary>
|
||||
Put = 2,
|
||||
/// <summary>DELETE request</summary>
|
||||
Delete = 3,
|
||||
/// <summary>HEAD request</summary>
|
||||
Head = 4,
|
||||
/// <summary>OPTIONS request</summary>
|
||||
Options = 5,
|
||||
/// <summary>PATCH request</summary>
|
||||
Patch = 6
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP client</summary>
|
||||
public class HttpClient
|
||||
{
|
||||
/// <summary>Gets or sets the keep-alive setting</summary>
|
||||
public bool KeepAlive { get; set; } = false;
|
||||
|
||||
/// <summary>Gets or sets the port number</summary>
|
||||
public int Port { get; set; } = 80;
|
||||
|
||||
/// <summary>Dispatch HTTP request</summary>
|
||||
/// <param name="request">HTTP request</param>
|
||||
/// <param name="callback">Callback for response</param>
|
||||
public void Dispatch(HttpClientRequest request, Action<HttpClientResponse> callback)
|
||||
{
|
||||
// Mock implementation - invoke callback with empty response
|
||||
var response = new HttpClientResponse();
|
||||
callback?.Invoke(response);
|
||||
}
|
||||
|
||||
/// <summary>Dispatches HTTP request synchronously</summary>
|
||||
/// <param name="request">HTTP request</param>
|
||||
/// <returns>HTTP response</returns>
|
||||
public HttpClientResponse Dispatch(HttpClientRequest request)
|
||||
{
|
||||
// Mock implementation - return empty response
|
||||
return new HttpClientResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP client request</summary>
|
||||
public class HttpClientRequest
|
||||
{
|
||||
/// <summary>Gets or sets the URL parser</summary>
|
||||
public Crestron.SimplSharp.Net.Http.UrlParserResult Url { get; set; } = new Crestron.SimplSharp.Net.Http.UrlParserResult();
|
||||
|
||||
/// <summary>Gets or sets the HTTP method</summary>
|
||||
public RequestType RequestType { get; set; } = RequestType.Get;
|
||||
|
||||
/// <summary>Gets or sets the content data</summary>
|
||||
public string ContentString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the headers collection</summary>
|
||||
public HttpHeaderCollection Header { get; } = new HttpHeaderCollection();
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP client response</summary>
|
||||
public class HttpClientResponse
|
||||
{
|
||||
/// <summary>Gets the response code</summary>
|
||||
public int Code { get; set; } = 200;
|
||||
|
||||
/// <summary>Gets the response content</summary>
|
||||
public string ContentString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the response data as bytes</summary>
|
||||
public byte[] ContentBytes { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets the headers collection</summary>
|
||||
public HttpHeaderCollection Header { get; } = new HttpHeaderCollection();
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP header collection</summary>
|
||||
public class HttpHeaderCollection
|
||||
{
|
||||
private readonly Dictionary<string, string> _headers = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>Gets or sets the content type</summary>
|
||||
public string ContentType
|
||||
{
|
||||
get => _headers.TryGetValue("Content-Type", out var value) ? value : string.Empty;
|
||||
set => _headers["Content-Type"] = value;
|
||||
}
|
||||
|
||||
/// <summary>Sets a header value</summary>
|
||||
/// <param name="name">Header name</param>
|
||||
/// <param name="value">Header value</param>
|
||||
public void SetHeaderValue(string name, string value)
|
||||
{
|
||||
_headers[name] = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets a header value</summary>
|
||||
/// <param name="name">Header name</param>
|
||||
/// <returns>Header value or empty string if not found</returns>
|
||||
public string GetHeaderValue(string name)
|
||||
{
|
||||
return _headers.TryGetValue(name, out var value) ? value : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace Crestron.SimplSharp.Net.Https
|
||||
{
|
||||
/// <summary>HTTPS request types</summary>
|
||||
public enum RequestType
|
||||
{
|
||||
/// <summary>GET request</summary>
|
||||
Get = 0,
|
||||
/// <summary>POST request</summary>
|
||||
Post = 1,
|
||||
/// <summary>PUT request</summary>
|
||||
Put = 2,
|
||||
/// <summary>DELETE request</summary>
|
||||
Delete = 3,
|
||||
/// <summary>HEAD request</summary>
|
||||
Head = 4,
|
||||
/// <summary>OPTIONS request</summary>
|
||||
Options = 5,
|
||||
/// <summary>PATCH request</summary>
|
||||
Patch = 6
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTPS client</summary>
|
||||
public class HttpsClient
|
||||
{
|
||||
/// <summary>Gets or sets the keep-alive setting</summary>
|
||||
public bool KeepAlive { get; set; } = false;
|
||||
|
||||
/// <summary>Gets or sets the host verification setting</summary>
|
||||
public bool HostVerification { get; set; } = false;
|
||||
|
||||
/// <summary>Gets or sets the peer verification setting</summary>
|
||||
public bool PeerVerification { get; set; } = false;
|
||||
|
||||
/// <summary>Dispatch HTTPS request</summary>
|
||||
/// <param name="request">HTTPS request</param>
|
||||
/// <param name="callback">Callback for response</param>
|
||||
public void Dispatch(HttpsClientRequest request, Action<HttpsClientResponse> callback)
|
||||
{
|
||||
// Mock implementation - invoke callback with empty response
|
||||
var response = new HttpsClientResponse();
|
||||
callback?.Invoke(response);
|
||||
}
|
||||
|
||||
/// <summary>Dispatches HTTPS request synchronously</summary>
|
||||
/// <param name="request">HTTPS request</param>
|
||||
/// <returns>HTTPS response</returns>
|
||||
public HttpsClientResponse Dispatch(HttpsClientRequest request)
|
||||
{
|
||||
// Mock implementation - return empty response
|
||||
return new HttpsClientResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTPS client request</summary>
|
||||
public class HttpsClientRequest
|
||||
{
|
||||
/// <summary>Gets or sets the URL parser</summary>
|
||||
public Crestron.SimplSharp.Net.Https.UrlParserResult Url { get; set; } = new Crestron.SimplSharp.Net.Https.UrlParserResult();
|
||||
|
||||
/// <summary>Gets or sets the HTTP method</summary>
|
||||
public RequestType RequestType { get; set; } = RequestType.Get;
|
||||
|
||||
/// <summary>Gets or sets the content data</summary>
|
||||
public string ContentString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the headers collection</summary>
|
||||
public HttpsHeaderCollection Header { get; } = new HttpsHeaderCollection();
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTPS client response</summary>
|
||||
public class HttpsClientResponse
|
||||
{
|
||||
/// <summary>Gets the response code</summary>
|
||||
public int Code { get; set; } = 200;
|
||||
|
||||
/// <summary>Gets the response content</summary>
|
||||
public string ContentString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the response data as bytes</summary>
|
||||
public byte[] ContentBytes { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>Gets the headers collection</summary>
|
||||
public HttpsHeaderCollection Header { get; } = new HttpsHeaderCollection();
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTPS header collection</summary>
|
||||
public class HttpsHeaderCollection
|
||||
{
|
||||
private readonly Dictionary<string, string> _headers = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>Gets or sets the content type</summary>
|
||||
public string ContentType
|
||||
{
|
||||
get => _headers.TryGetValue("Content-Type", out var value) ? value : string.Empty;
|
||||
set => _headers["Content-Type"] = value;
|
||||
}
|
||||
|
||||
/// <summary>Sets a header value</summary>
|
||||
/// <param name="name">Header name</param>
|
||||
/// <param name="value">Header value</param>
|
||||
public void SetHeaderValue(string name, string value)
|
||||
{
|
||||
_headers[name] = value;
|
||||
}
|
||||
|
||||
/// <summary>Adds a header</summary>
|
||||
/// <param name="header">Header to add</param>
|
||||
public void AddHeader(HttpsHeader header)
|
||||
{
|
||||
_headers[header.Name] = header.Value;
|
||||
}
|
||||
|
||||
/// <summary>Gets a header value</summary>
|
||||
/// <param name="name">Header name</param>
|
||||
/// <returns>Header value or empty string if not found</returns>
|
||||
public string GetHeaderValue(string name)
|
||||
{
|
||||
return _headers.TryGetValue(name, out var value) ? value : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTPS header</summary>
|
||||
public class HttpsHeader
|
||||
{
|
||||
/// <summary>Gets the header name</summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>Gets the header value</summary>
|
||||
public string Value { get; private set; }
|
||||
|
||||
/// <summary>Initializes a new instance of HttpsHeader</summary>
|
||||
/// <param name="name">Header name</param>
|
||||
/// <param name="value">Header value</param>
|
||||
public HttpsHeader(string name, string value)
|
||||
{
|
||||
Name = name ?? string.Empty;
|
||||
Value = value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Mock HTTP exception</summary>
|
||||
public class HttpException : Exception
|
||||
{
|
||||
/// <summary>Gets the HTTP response</summary>
|
||||
public HttpsClientResponse Response { get; }
|
||||
|
||||
/// <summary>Initializes a new instance of HttpException</summary>
|
||||
public HttpException() : base()
|
||||
{
|
||||
Response = new HttpsClientResponse();
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpException</summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
public HttpException(string message) : base(message)
|
||||
{
|
||||
Response = new HttpsClientResponse();
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpException</summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
/// <param name="innerException">Inner exception</param>
|
||||
public HttpException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
Response = new HttpsClientResponse();
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of HttpException</summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
/// <param name="response">HTTP response</param>
|
||||
public HttpException(string message, HttpsClientResponse response) : base(message)
|
||||
{
|
||||
Response = response ?? new HttpsClientResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>2.0.0-local</Version>
|
||||
<Version>3.0.0-local</Version>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<Authors>PepperDash Technology</Authors>
|
||||
<Company>PepperDash Technology</Company>
|
||||
@@ -13,6 +13,8 @@
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\LICENSE.md" Pack="true" PackagePath=""/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library'">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>build;</PackagePath>
|
||||
</None>
|
||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>build;</PackagePath>
|
||||
@@ -9,30 +13,41 @@
|
||||
<PackagePath>build;</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="$(ProjectType) == 'Library'">
|
||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz</FileName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz</FileName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(ProjectType) == 'Program'">
|
||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
|
||||
</PropertyGroup>
|
||||
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
|
||||
|
||||
<Target Name="DeleteCLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Library' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">
|
||||
<Message Text="Creating CPLZ $(TargetDir)"></Message>
|
||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))" />
|
||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
||||
<Copy SourceFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" />
|
||||
</Target>
|
||||
<Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">
|
||||
<Message Text="Creating CPLZ $(TargetDir)"></Message>
|
||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))" />
|
||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
||||
<Copy SourceFiles="$(PackageOutputPath)\$(TargetName).$(Version).$(TargetFramework).cplz" DestinationFiles="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" />
|
||||
</Target>
|
||||
<Target Name="Copy CLZ" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Library')">
|
||||
<Message Text="Copying CLZ"></Message>
|
||||
<Move SourceFiles="$(TargetDir)\$(TargetName).clz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).$(TargetFramework).clz"/>
|
||||
|
||||
@@ -8,8 +8,8 @@ using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the string event handler for line events on the gather
|
||||
/// </summary>
|
||||
@@ -30,7 +30,7 @@ namespace PepperDash.Core
|
||||
/// <summary>
|
||||
/// The communication port that this gathers on
|
||||
/// </summary>
|
||||
public ICommunicationReceiver Port { get; private set; }
|
||||
public ICommunicationReceiver Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default false. If true, the delimiter will be included in the line output
|
||||
@@ -67,22 +67,22 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
||||
:this(port, new string[] { delimiter} )
|
||||
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
||||
:this(port, new string[] { delimiter} )
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for using an array of string delimiters
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiters"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
||||
{
|
||||
Port = port;
|
||||
StringDelimiters = delimiters;
|
||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructor for using an array of string delimiters
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiters"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
||||
{
|
||||
Port = port;
|
||||
StringDelimiters = delimiters;
|
||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
|
||||
@@ -136,35 +136,35 @@ namespace PepperDash.Core
|
||||
ReceiveBuffer.Append(args.Text);
|
||||
var str = ReceiveBuffer.ToString();
|
||||
|
||||
// Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a
|
||||
// Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a
|
||||
|
||||
// RX: DEV
|
||||
// Split: (1) "DEV"
|
||||
// RX: I
|
||||
// Split: (1) "DEVI"
|
||||
// RX: CE get version
|
||||
// Split: (1) "DEVICE get version"
|
||||
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
|
||||
// Split: (2) DEVICE get version, +OK "value":"1234"
|
||||
// RX: DEV
|
||||
// Split: (1) "DEV"
|
||||
// RX: I
|
||||
// Split: (1) "DEVI"
|
||||
// RX: CE get version
|
||||
// Split: (1) "DEVICE get version"
|
||||
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
|
||||
// Split: (2) DEVICE get version, +OK "value":"1234"
|
||||
|
||||
// Iterate the delimiters and fire an event for any matching delimiter
|
||||
foreach (var delimiter in StringDelimiters)
|
||||
// Iterate the delimiters and fire an event for any matching delimiter
|
||||
foreach (var delimiter in StringDelimiters)
|
||||
{
|
||||
var lines = Regex.Split(str, delimiter);
|
||||
if (lines.Length == 1)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < lines.Length - 1; i++)
|
||||
{
|
||||
var lines = Regex.Split(str, delimiter);
|
||||
if (lines.Length == 1)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < lines.Length - 1; i++)
|
||||
{
|
||||
string strToSend = null;
|
||||
if (IncludeDelimiter)
|
||||
strToSend = lines[i] + delimiter;
|
||||
else
|
||||
strToSend = lines[i];
|
||||
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter));
|
||||
}
|
||||
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
|
||||
string strToSend = null;
|
||||
if (IncludeDelimiter)
|
||||
strToSend = lines[i] + delimiter;
|
||||
else
|
||||
strToSend = lines[i];
|
||||
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter));
|
||||
}
|
||||
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,5 +175,4 @@ namespace PepperDash.Core
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,173 +5,172 @@ using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
||||
/// </summary>
|
||||
public class CommunicationStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
||||
/// Device Key that this instance configures
|
||||
/// </summary>
|
||||
public class CommunicationStreamDebugging
|
||||
public string ParentDeviceKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timer to disable automatically if not manually disabled
|
||||
/// </summary>
|
||||
private CTimer DebugExpiryPeriod;
|
||||
|
||||
/// <summary>
|
||||
/// The current debug setting
|
||||
/// </summary>
|
||||
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
||||
|
||||
private uint _DebugTimeoutInMs;
|
||||
private const uint _DefaultDebugTimeoutMin = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in Minutes
|
||||
/// </summary>
|
||||
public uint DebugTimeoutMinutes
|
||||
{
|
||||
/// <summary>
|
||||
/// Device Key that this instance configures
|
||||
/// </summary>
|
||||
public string ParentDeviceKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timer to disable automatically if not manually disabled
|
||||
/// </summary>
|
||||
private CTimer DebugExpiryPeriod;
|
||||
|
||||
/// <summary>
|
||||
/// The current debug setting
|
||||
/// </summary>
|
||||
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
||||
|
||||
private uint _DebugTimeoutInMs;
|
||||
private const uint _DefaultDebugTimeoutMin = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in Minutes
|
||||
/// </summary>
|
||||
public uint DebugTimeoutMinutes
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
return _DebugTimeoutInMs/60000;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that receive stream debugging is enabled
|
||||
/// </summary>
|
||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that transmit stream debugging is enabled
|
||||
/// </summary>
|
||||
public bool TxStreamDebuggingIsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="parentDeviceKey"></param>
|
||||
public CommunicationStreamDebugging(string parentDeviceKey)
|
||||
{
|
||||
ParentDeviceKey = parentDeviceKey;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
||||
/// </summary>
|
||||
/// <param name="setting"></param>
|
||||
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
||||
{
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debugging setting for the specified number of minutes
|
||||
/// </summary>
|
||||
/// <param name="setting"></param>
|
||||
/// <param name="minutes"></param>
|
||||
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
||||
{
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
_DebugTimeoutInMs = minutes * 60000;
|
||||
|
||||
StopDebugTimer();
|
||||
|
||||
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||
RxStreamDebuggingIsEnabled = true;
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
|
||||
TxStreamDebuggingIsEnabled = true;
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled debugging
|
||||
/// </summary>
|
||||
private void DisableDebugging()
|
||||
{
|
||||
StopDebugTimer();
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
|
||||
}
|
||||
|
||||
private void StopDebugTimer()
|
||||
{
|
||||
RxStreamDebuggingIsEnabled = false;
|
||||
TxStreamDebuggingIsEnabled = false;
|
||||
|
||||
if (DebugExpiryPeriod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugExpiryPeriod.Stop();
|
||||
DebugExpiryPeriod.Dispose();
|
||||
DebugExpiryPeriod = null;
|
||||
return _DebugTimeoutInMs/60000;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging
|
||||
/// Indicates that receive stream debugging is enabled
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingSetting
|
||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that transmit stream debugging is enabled
|
||||
/// </summary>
|
||||
public bool TxStreamDebuggingIsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="parentDeviceKey"></param>
|
||||
public CommunicationStreamDebugging(string parentDeviceKey)
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug off
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Debug received data
|
||||
/// </summary>
|
||||
Rx = 1,
|
||||
/// <summary>
|
||||
/// Debug transmitted data
|
||||
/// </summary>
|
||||
Tx = 2,
|
||||
/// <summary>
|
||||
/// Debug both received and transmitted data
|
||||
/// </summary>
|
||||
Both = Rx | Tx
|
||||
ParentDeviceKey = parentDeviceKey;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
||||
/// </summary>
|
||||
/// <param name="setting"></param>
|
||||
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
||||
{
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging response types
|
||||
/// Sets the debugging setting for the specified number of minutes
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
/// <param name="setting"></param>
|
||||
/// <param name="minutes"></param>
|
||||
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text,
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
_DebugTimeoutInMs = minutes * 60000;
|
||||
|
||||
StopDebugTimer();
|
||||
|
||||
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||
RxStreamDebuggingIsEnabled = true;
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
|
||||
TxStreamDebuggingIsEnabled = true;
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled debugging
|
||||
/// </summary>
|
||||
private void DisableDebugging()
|
||||
{
|
||||
StopDebugTimer();
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
|
||||
}
|
||||
|
||||
private void StopDebugTimer()
|
||||
{
|
||||
RxStreamDebuggingIsEnabled = false;
|
||||
TxStreamDebuggingIsEnabled = false;
|
||||
|
||||
if (DebugExpiryPeriod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugExpiryPeriod.Stop();
|
||||
DebugExpiryPeriod.Dispose();
|
||||
DebugExpiryPeriod = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug off
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Debug received data
|
||||
/// </summary>
|
||||
Rx = 1,
|
||||
/// <summary>
|
||||
/// Debug transmitted data
|
||||
/// </summary>
|
||||
Tx = 2,
|
||||
/// <summary>
|
||||
/// Debug both received and transmitted data
|
||||
/// </summary>
|
||||
Both = Rx | Tx
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging response types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text,
|
||||
}
|
||||
|
||||
@@ -3,91 +3,90 @@ using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Config properties that indicate how to communicate with a device for control
|
||||
/// </summary>
|
||||
public class ControlPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Config properties that indicate how to communicate with a device for control
|
||||
/// The method of control
|
||||
/// </summary>
|
||||
public class ControlPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The method of control
|
||||
/// </summary>
|
||||
[JsonProperty("method")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eControlMethod Method { get; set; }
|
||||
[JsonProperty("method")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eControlMethod Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The key of the device that contains the control port
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ControlPortDevKey { get; set; }
|
||||
/// <summary>
|
||||
/// The key of the device that contains the control port
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ControlPortDevKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public uint? ControlPortNumber { get; set; }
|
||||
/// <summary>
|
||||
/// The number of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public uint? ControlPortNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public string ControlPortName { get; set; }
|
||||
/// <summary>
|
||||
/// The name of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public string ControlPortName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties for ethernet based communications
|
||||
/// </summary>
|
||||
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
||||
/// <summary>
|
||||
/// Properties for ethernet based communications
|
||||
/// </summary>
|
||||
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename and path for the IR file
|
||||
/// </summary>
|
||||
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string IrFile { get; set; }
|
||||
/// <summary>
|
||||
/// The filename and path for the IR file
|
||||
/// </summary>
|
||||
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string IrFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IpId of a Crestron device
|
||||
/// </summary>
|
||||
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string IpId { get; set; }
|
||||
/// <summary>
|
||||
/// The IpId of a Crestron device
|
||||
/// </summary>
|
||||
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string IpId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Readonly uint representation of the IpId
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
||||
/// <summary>
|
||||
/// Readonly uint representation of the IpId
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
||||
|
||||
/// <summary>
|
||||
/// Char indicating end of line
|
||||
/// </summary>
|
||||
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public char EndOfLineChar { get; set; }
|
||||
/// <summary>
|
||||
/// Char indicating end of line
|
||||
/// </summary>
|
||||
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public char EndOfLineChar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to Environment.NewLine;
|
||||
/// </summary>
|
||||
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string EndOfLineString { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to Environment.NewLine;
|
||||
/// </summary>
|
||||
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string EndOfLineString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates
|
||||
/// </summary>
|
||||
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DeviceReadyResponsePattern { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates
|
||||
/// </summary>
|
||||
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string DeviceReadyResponsePattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when communcating to programs running in VC-4
|
||||
/// </summary>
|
||||
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RoomId { get; set; }
|
||||
/// <summary>
|
||||
/// Used when communcating to programs running in VC-4
|
||||
/// </summary>
|
||||
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string RoomId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ControlPropertiesConfig()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ControlPropertiesConfig()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -16,28 +16,28 @@ using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for notifying of socket status changes
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for socket status changes
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Delegate for notifying of socket status changes
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for socket status changes
|
||||
/// </summary>
|
||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ISocketStatus Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||
{
|
||||
Client = client;
|
||||
@@ -46,105 +46,105 @@ namespace PepperDash.Core
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericSocketStatusChageEventArgs() { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for notifying of TCP Server state changes
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for TCP Server state changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ServerState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for notifying of TCP Server state changes
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for TCP Server state changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ServerState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
State = state;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerStateChangedEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for TCP Server socket status changes
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server socket status changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public object Socket { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
||||
{
|
||||
Socket = socket;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for TCP Server socket status changes
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server socket status changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public object Socket { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
||||
{
|
||||
Socket = socket;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
||||
{
|
||||
Socket = socket;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
Socket = socket;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server com method receive text
|
||||
/// </summary>
|
||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server com method receive text
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort ReceivedFromClientIndexShort
|
||||
{
|
||||
get
|
||||
@@ -153,99 +153,96 @@ namespace PepperDash.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
||||
{
|
||||
Text = text;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
||||
{
|
||||
Text = text;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server client ready for communication
|
||||
/// </summary>
|
||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsReady;
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server client ready for communication
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||
/// <param name="isReady"></param>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsReady;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="isReady"></param>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||
{
|
||||
IsReady = isReady;
|
||||
}
|
||||
IsReady = isReady;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for UDP connected
|
||||
/// </summary>
|
||||
public class GenericUdpConnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UConnected;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public GenericUdpConnectedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="uconnected"></param>
|
||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||
{
|
||||
UConnected = uconnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for UDP connected
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericUdpConnectedEventArgs : EventArgs
|
||||
/// <param name="connected"></param>
|
||||
public GenericUdpConnectedEventArgs(bool connected)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UConnected;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public GenericUdpConnectedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="uconnected"></param>
|
||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||
{
|
||||
UConnected = uconnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="connected"></param>
|
||||
public GenericUdpConnectedEventArgs(bool connected)
|
||||
{
|
||||
Connected = connected;
|
||||
}
|
||||
|
||||
Connected = connected;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,145 +8,145 @@ using PepperDash.Core.Logging;
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SPlusKey = "Uninitialized SshClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivers args with byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivered as text.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event when the connection status changes.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
///// <summary>
|
||||
/////
|
||||
///// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for server
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// And... Password for server. That was worth documenting!
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True when the server is connected - when status == 2.
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
// returns false if no client or not connected
|
||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
private const string SPlusKey = "Uninitialized SshClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivers args with byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivered as text.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event when the connection status changes.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
///// <summary>
|
||||
/////
|
||||
///// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for server
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// And... Password for server. That was worth documenting!
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True when the server is connected - when status == 2.
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
get { return _ClientStatus; }
|
||||
private set
|
||||
{
|
||||
// returns false if no client or not connected
|
||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
if (_ClientStatus == value)
|
||||
return;
|
||||
_ClientStatus = value;
|
||||
OnConnectionChange();
|
||||
}
|
||||
}
|
||||
SocketStatus _ClientStatus;
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
/// and IsConnected with be true when this == 2.
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)_ClientStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get { return _ClientStatus; }
|
||||
private set
|
||||
{
|
||||
if (_ClientStatus == value)
|
||||
return;
|
||||
_ClientStatus = value;
|
||||
OnConnectionChange();
|
||||
}
|
||||
}
|
||||
SocketStatus _ClientStatus;
|
||||
/// <summary>
|
||||
/// Determines whether client will attempt reconnection on failure. Default is true
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
/// and IsConnected with be true when this == 2.
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)_ClientStatus; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Will be set and unset by connect and disconnect only
|
||||
/// </summary>
|
||||
public bool ConnectEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether client will attempt reconnection on failure. Default is true
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
/// </summary>
|
||||
public ushort UAutoReconnect
|
||||
{
|
||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will be set and unset by connect and disconnect only
|
||||
/// </summary>
|
||||
public bool ConnectEnabled { get; private set; }
|
||||
/// <summary>
|
||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
||||
/// Set to 5000 by default
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
/// </summary>
|
||||
public ushort UAutoReconnect
|
||||
{
|
||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
SshClient Client;
|
||||
|
||||
/// <summary>
|
||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
||||
/// Set to 5000 by default
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
ShellStream TheStream;
|
||||
|
||||
SshClient Client;
|
||||
CTimer ReconnectTimer;
|
||||
|
||||
ShellStream TheStream;
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
//private CCriticalSection connectLock = new CCriticalSection();
|
||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
|
||||
CTimer ReconnectTimer;
|
||||
private bool DisconnectLogged = false;
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
//private CCriticalSection connectLock = new CCriticalSection();
|
||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
|
||||
private bool DisconnectLogged = false;
|
||||
|
||||
/// <summary>
|
||||
/// Typical constructor.
|
||||
/// </summary>
|
||||
public GenericSshClient(string key, string hostname, int port, string username, string password) :
|
||||
base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
/// <summary>
|
||||
/// Typical constructor.
|
||||
/// </summary>
|
||||
public GenericSshClient(string key, string hostname, int port, string username, string password) :
|
||||
base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
Key = key;
|
||||
Hostname = hostname;
|
||||
@@ -155,12 +155,12 @@ namespace PepperDash.Core
|
||||
Password = password;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
@@ -173,13 +173,13 @@ namespace PepperDash.Core
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -192,137 +192,137 @@ namespace PepperDash.Core
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
Disconnect();
|
||||
}
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to the server, using the provided properties.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
/// <summary>
|
||||
/// Connect to the server, using the provided properties.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
// Don't go unless everything is here
|
||||
if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535
|
||||
|| Username == null || Password == null)
|
||||
{
|
||||
// Don't go unless everything is here
|
||||
if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535
|
||||
|| Username == null || Password == null)
|
||||
this.LogError("Connect failed. Check hostname, port, username and password are set or not null");
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectEnabled = true;
|
||||
|
||||
try
|
||||
{
|
||||
connectLock.Wait();
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogError("Connect failed. Check hostname, port, username and password are set or not null");
|
||||
return;
|
||||
this.LogDebug("Connection already connected. Exiting Connect");
|
||||
}
|
||||
|
||||
ConnectEnabled = true;
|
||||
|
||||
try
|
||||
else
|
||||
{
|
||||
connectLock.Wait();
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogDebug("Connection already connected. Exiting Connect");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Attempting connect");
|
||||
this.LogDebug("Attempting connect");
|
||||
|
||||
// Cancel reconnect if running.
|
||||
// Cancel reconnect if running.
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
}
|
||||
|
||||
// Cleanup the old client if it already exists
|
||||
if (Client != null)
|
||||
// Cleanup the old client if it already exists
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogDebug("Cleaning up disconnected client");
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
|
||||
// This handles both password and keyboard-interactive (like on OS-X, 'nixes)
|
||||
KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username);
|
||||
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
|
||||
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
|
||||
|
||||
this.LogDebug("Creating new SshClient");
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
||||
Client = new SshClient(connectionInfo);
|
||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
||||
|
||||
//Attempt to connect
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
||||
try
|
||||
{
|
||||
Client.Connect();
|
||||
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
|
||||
if (TheStream.DataAvailable)
|
||||
{
|
||||
this.LogDebug("Cleaning up disconnected client");
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
// empty the buffer if there is data
|
||||
string str = TheStream.Read();
|
||||
}
|
||||
TheStream.DataReceived += Stream_DataReceived;
|
||||
this.LogInformation("Connected");
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||
DisconnectLogged = false;
|
||||
}
|
||||
catch (SshConnectionException e)
|
||||
{
|
||||
var ie = e.InnerException; // The details are inside!!
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
|
||||
if (ie is SocketException)
|
||||
{
|
||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
||||
}
|
||||
|
||||
// This handles both password and keyboard-interactive (like on OS-X, 'nixes)
|
||||
KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username);
|
||||
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
|
||||
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
|
||||
|
||||
this.LogDebug("Creating new SshClient");
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
||||
Client = new SshClient(connectionInfo);
|
||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
||||
|
||||
//Attempt to connect
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
||||
try
|
||||
if (ie is System.Net.Sockets.SocketException socketException)
|
||||
{
|
||||
Client.Connect();
|
||||
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
|
||||
if (TheStream.DataAvailable)
|
||||
{
|
||||
// empty the buffer if there is data
|
||||
string str = TheStream.Read();
|
||||
}
|
||||
TheStream.DataReceived += Stream_DataReceived;
|
||||
this.LogInformation("Connected");
|
||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||
DisconnectLogged = false;
|
||||
this.LogException(ie, "Connection failure: Cannot reach {host} on {port}",
|
||||
Hostname, Port);
|
||||
}
|
||||
catch (SshConnectionException e)
|
||||
if (ie is SshAuthenticationException)
|
||||
{
|
||||
var ie = e.InnerException; // The details are inside!!
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
|
||||
if (ie is SocketException)
|
||||
{
|
||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
||||
}
|
||||
|
||||
if (ie is System.Net.Sockets.SocketException socketException)
|
||||
{
|
||||
this.LogException(ie, "Connection failure: Cannot reach {host} on {port}",
|
||||
Hostname, Port);
|
||||
}
|
||||
if (ie is SshAuthenticationException)
|
||||
{
|
||||
this.LogException(ie, "Authentication failure for username {userName}", Username);
|
||||
}
|
||||
else
|
||||
this.LogException(ie, "Error on connect");
|
||||
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
this.LogException(ie, "Authentication failure for username {userName}", Username);
|
||||
}
|
||||
catch(SshOperationTimeoutException ex)
|
||||
{
|
||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
||||
else
|
||||
this.LogException(ie, "Error on connect");
|
||||
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
this.LogException(e, "Unhandled exception on connect");
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
catch(SshOperationTimeoutException ex)
|
||||
{
|
||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
||||
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
this.LogException(e, "Unhandled exception on connect");
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Release();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the clients and put away it's resources.
|
||||
@@ -337,54 +337,54 @@ namespace PepperDash.Core
|
||||
// ReconnectTimer = null;
|
||||
}
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream, cleans up the client and sets it to null
|
||||
/// </summary>
|
||||
private void KillClient(SocketStatus status)
|
||||
/// <summary>
|
||||
/// Kills the stream, cleans up the client and sets it to null
|
||||
/// </summary>
|
||||
private void KillClient(SocketStatus status)
|
||||
{
|
||||
KillStream();
|
||||
|
||||
try
|
||||
{
|
||||
KillStream();
|
||||
|
||||
try
|
||||
if (Client != null)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
Client.ErrorOccurred -= Client_ErrorOccurred;
|
||||
Client.Disconnect();
|
||||
Client.Dispose();
|
||||
Client = null;
|
||||
ClientStatus = status;
|
||||
this.LogDebug("Disconnected");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex,"Exception in Kill Client");
|
||||
Client.ErrorOccurred -= Client_ErrorOccurred;
|
||||
Client.Disconnect();
|
||||
Client.Dispose();
|
||||
Client = null;
|
||||
ClientStatus = status;
|
||||
this.LogDebug("Disconnected");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex,"Exception in Kill Client");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
void KillStream()
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
if (TheStream != null)
|
||||
{
|
||||
if (TheStream != null)
|
||||
{
|
||||
TheStream.DataReceived -= Stream_DataReceived;
|
||||
TheStream.Close();
|
||||
TheStream.Dispose();
|
||||
TheStream = null;
|
||||
this.LogDebug("Disconnected stream");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception in Kill Stream:{0}");
|
||||
TheStream.DataReceived -= Stream_DataReceived;
|
||||
TheStream.Close();
|
||||
TheStream.Dispose();
|
||||
TheStream = null;
|
||||
this.LogDebug("Disconnected stream");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception in Kill Stream:{0}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -402,32 +402,32 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
{
|
||||
if (((ShellStream)sender).Length <= 0L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var response = ((ShellStream)sender).Read();
|
||||
|
||||
if (((ShellStream)sender).Length <= 0L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var response = ((ShellStream)sender).Read();
|
||||
|
||||
var bytesHandler = BytesReceived;
|
||||
|
||||
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(response);
|
||||
var bytes = Encoding.UTF8.GetBytes(response);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||
}
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -438,39 +438,39 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||
this.LogError("Disconnected by remote");
|
||||
else
|
||||
this.LogException(e.Exception, "Unhandled SSH client error");
|
||||
try
|
||||
{
|
||||
connectLock.Wait();
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Release();
|
||||
}
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
if (ConnectionChange != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||
this.LogError("Disconnected by remote");
|
||||
else
|
||||
this.LogException(e.Exception, "Unhandled SSH client error");
|
||||
try
|
||||
{
|
||||
connectLock.Wait();
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Release();
|
||||
}
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region IBasicCommunication Members
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
{
|
||||
if (ConnectionChange != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// Sends text to the server
|
||||
@@ -497,52 +497,52 @@ namespace PepperDash.Core
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
{
|
||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending text: '{message}'", text);
|
||||
this.LogException(ex, "Exception sending text: '{message}'", text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
TheStream.Write(bytes, 0, bytes.Length);
|
||||
TheStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Bytes");
|
||||
}
|
||||
TheStream.Write(bytes, 0, bytes.Length);
|
||||
TheStream.Flush();
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
else
|
||||
{
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Bytes");
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
}
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -553,40 +553,39 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection State
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Connection State
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connection Status represented as a ushort
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Connection Status represented as a ushort
|
||||
/// </summary>
|
||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// The client
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// The client
|
||||
/// </summary>
|
||||
public GenericSshClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status as represented by
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Socket Status as represented by
|
||||
/// </summary>
|
||||
public ushort Status { get { return Client.UStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
/// </summary>
|
||||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
/// </summary>
|
||||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,18 @@ using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to handle basic TCP/IP communications with a server
|
||||
/// </summary>
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A class to handle basic TCP/IP communications with a server
|
||||
/// </summary>
|
||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
{
|
||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
@@ -38,11 +38,11 @@ namespace PepperDash.Core
|
||||
|
||||
private string _hostname;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
@@ -58,25 +58,25 @@ namespace PepperDash.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
@@ -87,47 +87,47 @@ namespace PepperDash.Core
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// _client socket status Read only
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
/// and IsConnected would be true when this == 2.
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
get { return (ushort)ClientStatus; }
|
||||
}
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
/// and IsConnected would be true when this == 2.
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)ClientStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status text shows the message associated with socket status
|
||||
/// Status text shows the message associated with socket status
|
||||
/// </summary>
|
||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Ushort representation of client status
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[Obsolete]
|
||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||
|
||||
/// <summary>
|
||||
@@ -140,14 +140,14 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
/// </summary>
|
||||
public ushort UAutoReconnect
|
||||
{
|
||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
/// </summary>
|
||||
public ushort UAutoReconnect
|
||||
{
|
||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||
@@ -167,283 +167,283 @@ namespace PepperDash.Core
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
private CCriticalSection connectLock = new CCriticalSection();
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
private CCriticalSection connectLock = new CCriticalSection();
|
||||
|
||||
// private Timer for auto reconnect
|
||||
// private Timer for auto reconnect
|
||||
private CTimer RetryTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">unique string to differentiate between instances</param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">unique string to differentiate between instances</param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = bufferSize;
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = bufferSize;
|
||||
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public GenericTcpIpClient(string key)
|
||||
: base(key)
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public GenericTcpIpClient(string key)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
public GenericTcpIpClient()
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
public GenericTcpIpClient()
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
BufferSize = 2000;
|
||||
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles closing this up when the program shuts down
|
||||
/// </summary>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
/// <summary>
|
||||
/// Handles closing this up when the program shuts down
|
||||
/// </summary>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||
Deactivate();
|
||||
}
|
||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Dispose();
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Dispose();
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to connect to the server
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Attempts to connect to the server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (IsConnected)
|
||||
{
|
||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Stop retry timer if running
|
||||
RetryTimer.Stop();
|
||||
_client = new TCPClient(Hostname, Port, BufferSize);
|
||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
_client.SocketStatusChange += Client_SocketStatusChange;
|
||||
DisconnectCalledByUser = false;
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reconnect()
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
if (_client == null)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (IsConnected || DisconnectCalledByUser == true)
|
||||
{
|
||||
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Attempting reconnect now");
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to disconnect the client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
DisconnectCalledByUser = true;
|
||||
|
||||
// Stop trying reconnects, if we are
|
||||
RetryTimer.Stop();
|
||||
DisconnectClient();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the actual disconnect business
|
||||
/// </summary>
|
||||
public void DisconnectClient()
|
||||
try
|
||||
{
|
||||
if (_client != null)
|
||||
connectLock.Enter();
|
||||
if (IsConnected)
|
||||
{
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for connection attempt
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
void ConnectToServerCallback(TCPClient c)
|
||||
{
|
||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
||||
WaitAndTryReconnect();
|
||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
||||
//Stop retry timer if running
|
||||
RetryTimer.Stop();
|
||||
_client = new TCPClient(Hostname, Port, BufferSize);
|
||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
_client.SocketStatusChange += Client_SocketStatusChange;
|
||||
DisconnectCalledByUser = false;
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects, waits and attemtps to connect again
|
||||
/// </summary>
|
||||
private void Reconnect()
|
||||
{
|
||||
if (_client == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (IsConnected || DisconnectCalledByUser == true)
|
||||
{
|
||||
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Attempting reconnect now");
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to disconnect the client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
DisconnectCalledByUser = true;
|
||||
|
||||
// Stop trying reconnects, if we are
|
||||
RetryTimer.Stop();
|
||||
DisconnectClient();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the actual disconnect business
|
||||
/// </summary>
|
||||
public void DisconnectClient()
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for connection attempt
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
void ConnectToServerCallback(TCPClient c)
|
||||
{
|
||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
||||
WaitAndTryReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects, waits and attemtps to connect again
|
||||
/// </summary>
|
||||
void WaitAndTryReconnect()
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
connectLock.Enter();
|
||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
||||
{
|
||||
DisconnectClient();
|
||||
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
||||
RetryTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
DisconnectClient();
|
||||
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
||||
RetryTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recieves incoming data
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
/// <summary>
|
||||
/// Recieves incoming data
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(TCPClient client, int numBytes)
|
||||
{
|
||||
if (client != null)
|
||||
if (client != null)
|
||||
{
|
||||
if (numBytes > 0)
|
||||
{
|
||||
if (numBytes > 0)
|
||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
}
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
client.ReceiveDataAsync(Receive);
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
}
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -453,9 +453,9 @@ namespace PepperDash.Core
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
// Check debug level before processing byte array
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
if (_client != null)
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
@@ -472,35 +472,35 @@ namespace PepperDash.Core
|
||||
SendText(unescapedText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
if (_client != null)
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status Change Handler
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="clientSocketStatus"></param>
|
||||
/// <summary>
|
||||
/// Socket Status Change Handler
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="clientSocketStatus"></param>
|
||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||
{
|
||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
WaitAndTryReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
WaitAndTryReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
_client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
|
||||
var handler = ConnectionChange;
|
||||
if (handler != null)
|
||||
@@ -508,30 +508,30 @@ namespace PepperDash.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration properties for TCP/SSH Connections
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Configuration properties for TCP/SSH Connections
|
||||
/// </summary>
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Address to connect to
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Address to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port to connect to
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Port to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username credential
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Username credential
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Passord credential
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Passord credential
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -549,18 +549,16 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public TcpSshPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Username = "";
|
||||
Password = "";
|
||||
Username = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,389 +8,388 @@ using Crestron.SimplSharp.CrestronSockets;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.Logging;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Generic UDP Server device
|
||||
/// </summary>
|
||||
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
private const string SplusKey = "Uninitialized Udp Server";
|
||||
/// <summary>
|
||||
/// Generic UDP Server device
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
private const string SplusKey = "Uninitialized Udp Server";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
get
|
||||
{
|
||||
return Server.ClientStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)Server.ServerStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the UDP Server is enabled
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numeric value indicating
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return IsConnected ? (ushort)1 : (ushort)0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server
|
||||
/// </summary>
|
||||
public UDPServer Server { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
|
||||
/// </summary>
|
||||
public GenericUdpServer()
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
BufferSize = 5000;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="buffefSize"></param>
|
||||
public GenericUdpServer(string key, string address, int port, int buffefSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = buffefSize;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from S+ to initialize values
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
public void Initialize(string key, string address, ushort port)
|
||||
{
|
||||
Key = key;
|
||||
Hostname = address;
|
||||
UPort = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ethernetEventArgs"></param>
|
||||
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
||||
{
|
||||
// Re-enable the server if the link comes back up and the status should be connected
|
||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
|
||||
&& IsConnected)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
return;
|
||||
|
||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the UDP Server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (Server == null)
|
||||
{
|
||||
Server = new UDPServer();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
get
|
||||
{
|
||||
return Server.ServerStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)Server.ServerStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the UDP Server is enabled
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numeric value indicating
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return IsConnected ? (ushort)1 : (ushort)0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server
|
||||
/// </summary>
|
||||
public UDPServer Server { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
|
||||
/// </summary>
|
||||
public GenericUdpServer()
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
BufferSize = 5000;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="buffefSize"></param>
|
||||
public GenericUdpServer(string key, string address, int port, int buffefSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = buffefSize;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from S+ to initialize values
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
public void Initialize(string key, string address, ushort port)
|
||||
{
|
||||
Key = key;
|
||||
Hostname = address;
|
||||
UPort = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ethernetEventArgs"></param>
|
||||
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
||||
{
|
||||
// Re-enable the server if the link comes back up and the status should be connected
|
||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
|
||||
&& IsConnected)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
return;
|
||||
|
||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the UDP Server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (Server == null)
|
||||
{
|
||||
Server = new UDPServer();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var status = Server.EnableUDPServer(Hostname, Port);
|
||||
|
||||
Debug.Console(2, this, "SocketErrorCode: {0}", status);
|
||||
if (status == SocketErrorCodes.SOCKET_OK)
|
||||
IsConnected = true;
|
||||
|
||||
var handler = UpdateConnectionStatus;
|
||||
if (handler != null)
|
||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||
|
||||
// Start receiving data
|
||||
Server.ReceiveDataAsync(Receive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled the UDP Server
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
var status = Server.EnableUDPServer(Hostname, Port);
|
||||
|
||||
Debug.Console(2, this, "SocketErrorCode: {0}", status);
|
||||
if (status == SocketErrorCodes.SOCKET_OK)
|
||||
IsConnected = true;
|
||||
|
||||
var handler = UpdateConnectionStatus;
|
||||
if (handler != null)
|
||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||
|
||||
// Start receiving data
|
||||
Server.ReceiveDataAsync(Receive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled the UDP Server
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (Server != null)
|
||||
Server.DisableUDPServer();
|
||||
|
||||
IsConnected = false;
|
||||
|
||||
var handler = UpdateConnectionStatus;
|
||||
if (handler != null)
|
||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recursive method to receive data
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(UDPServer server, int numBytes)
|
||||
{
|
||||
Debug.Console(2, this, "Received {0} bytes", numBytes);
|
||||
|
||||
try
|
||||
{
|
||||
if(Server != null)
|
||||
Server.DisableUDPServer();
|
||||
if (numBytes <= 0)
|
||||
return;
|
||||
|
||||
IsConnected = false;
|
||||
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
|
||||
var sourcePort = Server.IPPortLastMessageReceivedFrom;
|
||||
var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
|
||||
var handler = UpdateConnectionStatus;
|
||||
if (handler != null)
|
||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||
}
|
||||
var dataRecivedExtra = DataRecievedExtra;
|
||||
if (dataRecivedExtra != null)
|
||||
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recursive method to receive data
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(UDPServer server, int numBytes)
|
||||
{
|
||||
Debug.Console(2, this, "Received {0} bytes", numBytes);
|
||||
|
||||
try
|
||||
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
if (numBytes <= 0)
|
||||
return;
|
||||
|
||||
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
|
||||
var sourcePort = Server.IPPortLastMessageReceivedFrom;
|
||||
var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
|
||||
var dataRecivedExtra = DataRecievedExtra;
|
||||
if (dataRecivedExtra != null)
|
||||
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
|
||||
|
||||
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
this.LogException(ex, "GenericUdpServer Receive error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.ReceiveDataAsync(Receive);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void SendText(string text)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
}
|
||||
this.LogException(ex, "GenericUdpServer Receive error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void SendText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string IpAddress { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||
{
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
if (IsConnected && Server != null)
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string IpAddress { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UdpServerPropertiesConfig
|
||||
/// <param name="text"></param>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UdpServerPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public UdpServerPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public UdpServerPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,58 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
|
||||
/// </summary>
|
||||
public class TcpClientConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
|
||||
/// TcpSsh Properties
|
||||
/// </summary>
|
||||
public class TcpClientConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// TcpSsh Properties
|
||||
/// </summary>
|
||||
[JsonProperty("control")]
|
||||
public ControlPropertiesConfig Control { get; set; }
|
||||
[JsonProperty("control")]
|
||||
public ControlPropertiesConfig Control { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
[JsonProperty("secure")]
|
||||
public bool Secure { get; set; }
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
[JsonProperty("secure")]
|
||||
public bool Secure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKeyRequired")]
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKeyRequired")]
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKey")]
|
||||
public string SharedKey { get; set; }
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKey")]
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
||||
/// heartbeats do not raise received events.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequired")]
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
/// <summary>
|
||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
||||
/// heartbeats do not raise received events.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequired")]
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatStringToMatch")]
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatStringToMatch")]
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
[JsonProperty("receiveQueueSize")]
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
[JsonProperty("receiveQueueSize")]
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
@@ -4,57 +4,56 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
||||
/// </summary>
|
||||
public class TcpServerConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
||||
/// Uique key
|
||||
/// </summary>
|
||||
public class TcpServerConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Uique key
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// Max Clients that the server will allow to connect.
|
||||
/// </summary>
|
||||
public ushort MaxClients { get; set; }
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
public bool Secure { get; set; }
|
||||
/// <summary>
|
||||
/// Port for the server to listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
/// <summary>
|
||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
||||
/// heartbeats do not raise received events.
|
||||
/// </summary>
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
/// <summary>
|
||||
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// Max Clients that the server will allow to connect.
|
||||
/// </summary>
|
||||
public ushort MaxClients { get; set; }
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
public bool Secure { get; set; }
|
||||
/// <summary>
|
||||
/// Port for the server to listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
/// <summary>
|
||||
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
|
||||
/// heartbeats do not raise received events.
|
||||
/// </summary>
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
/// <summary>
|
||||
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
@@ -4,76 +4,75 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Crestron Control Methods for a comm object
|
||||
/// </summary>
|
||||
public enum eControlMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Crestron Control Methods for a comm object
|
||||
///
|
||||
/// </summary>
|
||||
public enum eControlMethod
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// RS232/422/485
|
||||
/// </summary>
|
||||
Com,
|
||||
/// <summary>
|
||||
/// Crestron IpId (most Crestron ethernet devices)
|
||||
/// </summary>
|
||||
IpId,
|
||||
/// <summary>
|
||||
/// Crestron IpIdTcp (HD-MD series, etc.)
|
||||
/// </summary>
|
||||
IpidTcp,
|
||||
/// <summary>
|
||||
/// Crestron IR control
|
||||
/// </summary>
|
||||
IR,
|
||||
/// <summary>
|
||||
/// SSH client
|
||||
/// </summary>
|
||||
Ssh,
|
||||
/// <summary>
|
||||
/// TCP/IP client
|
||||
/// </summary>
|
||||
Tcpip,
|
||||
/// <summary>
|
||||
/// Telnet
|
||||
/// </summary>
|
||||
Telnet,
|
||||
/// <summary>
|
||||
/// Crestnet device
|
||||
/// </summary>
|
||||
Cresnet,
|
||||
/// <summary>
|
||||
/// CEC Control, via a DM HDMI port
|
||||
/// </summary>
|
||||
Cec,
|
||||
/// <summary>
|
||||
/// UDP Server
|
||||
/// </summary>
|
||||
Udp,
|
||||
/// <summary>
|
||||
/// HTTP client
|
||||
/// </summary>
|
||||
Http,
|
||||
/// <summary>
|
||||
/// HTTPS client
|
||||
/// </summary>
|
||||
Https,
|
||||
/// <summary>
|
||||
/// Websocket client
|
||||
/// </summary>
|
||||
Ws,
|
||||
/// <summary>
|
||||
/// Secure Websocket client
|
||||
/// </summary>
|
||||
Wss,
|
||||
/// <summary>
|
||||
/// Secure TCP/IP
|
||||
/// </summary>
|
||||
SecureTcpIp
|
||||
}
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// RS232/422/485
|
||||
/// </summary>
|
||||
Com,
|
||||
/// <summary>
|
||||
/// Crestron IpId (most Crestron ethernet devices)
|
||||
/// </summary>
|
||||
IpId,
|
||||
/// <summary>
|
||||
/// Crestron IpIdTcp (HD-MD series, etc.)
|
||||
/// </summary>
|
||||
IpidTcp,
|
||||
/// <summary>
|
||||
/// Crestron IR control
|
||||
/// </summary>
|
||||
IR,
|
||||
/// <summary>
|
||||
/// SSH client
|
||||
/// </summary>
|
||||
Ssh,
|
||||
/// <summary>
|
||||
/// TCP/IP client
|
||||
/// </summary>
|
||||
Tcpip,
|
||||
/// <summary>
|
||||
/// Telnet
|
||||
/// </summary>
|
||||
Telnet,
|
||||
/// <summary>
|
||||
/// Crestnet device
|
||||
/// </summary>
|
||||
Cresnet,
|
||||
/// <summary>
|
||||
/// CEC Control, via a DM HDMI port
|
||||
/// </summary>
|
||||
Cec,
|
||||
/// <summary>
|
||||
/// UDP Server
|
||||
/// </summary>
|
||||
Udp,
|
||||
/// <summary>
|
||||
/// HTTP client
|
||||
/// </summary>
|
||||
Http,
|
||||
/// <summary>
|
||||
/// HTTPS client
|
||||
/// </summary>
|
||||
Https,
|
||||
/// <summary>
|
||||
/// Websocket client
|
||||
/// </summary>
|
||||
Ws,
|
||||
/// <summary>
|
||||
/// Secure Websocket client
|
||||
/// </summary>
|
||||
Wss,
|
||||
/// <summary>
|
||||
/// Secure TCP/IP
|
||||
/// </summary>
|
||||
SecureTcpIp
|
||||
}
|
||||
@@ -7,74 +7,74 @@ using Crestron.SimplSharp.CrestronSockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// An incoming communication stream
|
||||
/// </summary>
|
||||
public interface ICommunicationReceiver : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// An incoming communication stream
|
||||
/// Notifies of bytes received
|
||||
/// </summary>
|
||||
public interface ICommunicationReceiver : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies of bytes received
|
||||
/// </summary>
|
||||
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Notifies of text received
|
||||
/// </summary>
|
||||
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Notifies of text received
|
||||
/// </summary>
|
||||
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates connection status
|
||||
/// </summary>
|
||||
[JsonProperty("isConnected")]
|
||||
bool IsConnected { get; }
|
||||
/// <summary>
|
||||
/// Connect to the device
|
||||
/// </summary>
|
||||
void Connect();
|
||||
/// <summary>
|
||||
/// Disconnect from the device
|
||||
/// </summary>
|
||||
void Disconnect();
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicates connection status
|
||||
/// </summary>
|
||||
[JsonProperty("isConnected")]
|
||||
bool IsConnected { get; }
|
||||
/// <summary>
|
||||
/// Connect to the device
|
||||
/// </summary>
|
||||
void Connect();
|
||||
/// <summary>
|
||||
/// Disconnect from the device
|
||||
/// </summary>
|
||||
void Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device that uses basic connection
|
||||
/// </summary>
|
||||
public interface IBasicCommunication : ICommunicationReceiver
|
||||
public interface IBasicCommunication : ICommunicationReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Send text to the device
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <summary>
|
||||
/// Send text to the device
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
void SendText(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Send bytes to the device
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <summary>
|
||||
/// Send bytes to the device
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
void SendBytes(byte[] bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||
/// </summary>
|
||||
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||
/// </summary>
|
||||
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device with stream debugging capablities
|
||||
/// </summary>
|
||||
public interface IStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a device with stream debugging capablities
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public interface IStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
[JsonProperty("streamDebugging")]
|
||||
CommunicationStreamDebugging StreamDebugging { get; }
|
||||
}
|
||||
[JsonProperty("streamDebugging")]
|
||||
CommunicationStreamDebugging StreamDebugging { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
|
||||
@@ -82,41 +82,41 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public interface ISocketStatus : IBasicCommunication
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies of socket status changes
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Notifies of socket status changes
|
||||
/// </summary>
|
||||
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
/// The current socket status of the client
|
||||
/// </summary>
|
||||
[JsonProperty("clientStatus")]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
SocketStatus ClientStatus { get; }
|
||||
/// <summary>
|
||||
/// The current socket status of the client
|
||||
/// </summary>
|
||||
[JsonProperty("clientStatus")]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
||||
/// </summary>
|
||||
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a device that implements ISocketStatus and IStreamDebugging
|
||||
/// </summary>
|
||||
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a device that can automatically attempt to reconnect
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Describes a device that can automatically attempt to reconnect
|
||||
/// </summary>
|
||||
public interface IAutoReconnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Enable automatic recconnect
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnect")]
|
||||
/// <summary>
|
||||
/// Enable automatic recconnect
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnect")]
|
||||
bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// Interval in ms to attempt automatic recconnections
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnectIntervalMs")]
|
||||
/// <summary>
|
||||
/// Interval in ms to attempt automatic recconnections
|
||||
/// </summary>
|
||||
[JsonProperty("autoReconnectIntervalMs")]
|
||||
int AutoReconnectIntervalMs { get; set; }
|
||||
}
|
||||
|
||||
@@ -125,14 +125,14 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public enum eGenericCommMethodStatusChangeType
|
||||
{
|
||||
/// <summary>
|
||||
/// Connected
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Connected
|
||||
/// </summary>
|
||||
Connected,
|
||||
/// <summary>
|
||||
/// Disconnected
|
||||
/// </summary>
|
||||
Disconnected
|
||||
/// <summary>
|
||||
/// Disconnected
|
||||
/// </summary>
|
||||
Disconnected
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,15 +147,15 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||
{
|
||||
Bytes = bytes;
|
||||
@@ -172,33 +172,33 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Delimiter { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Delimiter { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
||||
:this(text)
|
||||
{
|
||||
Delimiter = delimiter;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text, string delimiter)
|
||||
:this(text)
|
||||
{
|
||||
Delimiter = delimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
@@ -213,35 +213,34 @@ namespace PepperDash.Core
|
||||
/// </summary>
|
||||
public class ComTextHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets escaped text for a byte array
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
/// <summary>
|
||||
/// Gets escaped text for a byte array
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetEscapedText(byte[] bytes)
|
||||
{
|
||||
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets escaped text for a string
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <summary>
|
||||
/// Gets escaped text for a string
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetEscapedText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets debug text for a string
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDebugText(string text)
|
||||
{
|
||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets debug text for a string
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDebugText(string text)
|
||||
{
|
||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||
}
|
||||
}
|
||||
@@ -1,235 +1,234 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core.Config
|
||||
namespace PepperDash.Core.Config;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a Portal formatted config file
|
||||
/// </summary>
|
||||
public class PortalConfigReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a Portal formatted config file
|
||||
/// </summary>
|
||||
public class PortalConfigReader
|
||||
/// <summary>
|
||||
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
|
||||
/// </summary>
|
||||
/// <returns>JObject of config file</returns>
|
||||
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
|
||||
/// </summary>
|
||||
/// <returns>JObject of config file</returns>
|
||||
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
|
||||
try
|
||||
{
|
||||
try
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Error,
|
||||
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
|
||||
}
|
||||
|
||||
using (StreamReader fs = new StreamReader(filePath))
|
||||
{
|
||||
var jsonObj = JObject.Parse(fs.ReadToEnd());
|
||||
if (jsonObj["template"] != null && jsonObj["system"] != null)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Error,
|
||||
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
|
||||
// it's a double-config, merge it.
|
||||
var merged = MergeConfigs(jsonObj);
|
||||
if (jsonObj["system_url"] != null)
|
||||
{
|
||||
merged["systemUrl"] = jsonObj["system_url"].Value<string>();
|
||||
}
|
||||
|
||||
if (jsonObj["template_url"] != null)
|
||||
{
|
||||
merged["templateUrl"] = jsonObj["template_url"].Value<string>();
|
||||
}
|
||||
|
||||
jsonObj = merged;
|
||||
}
|
||||
|
||||
using (StreamReader fs = new StreamReader(filePath))
|
||||
using (StreamWriter fw = new StreamWriter(savePath))
|
||||
{
|
||||
var jsonObj = JObject.Parse(fs.ReadToEnd());
|
||||
if(jsonObj["template"] != null && jsonObj["system"] != null)
|
||||
{
|
||||
// it's a double-config, merge it.
|
||||
var merged = MergeConfigs(jsonObj);
|
||||
if (jsonObj["system_url"] != null)
|
||||
{
|
||||
merged["systemUrl"] = jsonObj["system_url"].Value<string>();
|
||||
}
|
||||
|
||||
if (jsonObj["template_url"] != null)
|
||||
{
|
||||
merged["templateUrl"] = jsonObj["template_url"].Value<string>();
|
||||
}
|
||||
|
||||
jsonObj = merged;
|
||||
}
|
||||
|
||||
using (StreamWriter fw = new StreamWriter(savePath))
|
||||
{
|
||||
fw.Write(jsonObj.ToString(Formatting.Indented));
|
||||
Debug.LogMessage(LogEventLevel.Debug, "JSON config merged and saved to {0}", savePath);
|
||||
}
|
||||
|
||||
fw.Write(jsonObj.ToString(Formatting.Indented));
|
||||
Debug.LogMessage(LogEventLevel.Debug, "JSON config merged and saved to {0}", savePath);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "ERROR: Config load failed");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="doubleConfig"></param>
|
||||
/// <returns></returns>
|
||||
public static JObject MergeConfigs(JObject doubleConfig)
|
||||
catch (Exception e)
|
||||
{
|
||||
var system = JObject.FromObject(doubleConfig["system"]);
|
||||
var template = JObject.FromObject(doubleConfig["template"]);
|
||||
var merged = new JObject();
|
||||
|
||||
// Put together top-level objects
|
||||
if (system["info"] != null)
|
||||
merged.Add("info", Merge(template["info"], system["info"], "infO"));
|
||||
else
|
||||
merged.Add("info", template["info"]);
|
||||
|
||||
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
|
||||
system["devices"] as JArray, "key", "devices"));
|
||||
|
||||
if (system["rooms"] == null)
|
||||
merged.Add("rooms", template["rooms"]);
|
||||
else
|
||||
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
|
||||
system["rooms"] as JArray, "key", "rooms"));
|
||||
|
||||
if (system["sourceLists"] == null)
|
||||
merged.Add("sourceLists", template["sourceLists"]);
|
||||
else
|
||||
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
|
||||
|
||||
if (system["destinationLists"] == null)
|
||||
merged.Add("destinationLists", template["destinationLists"]);
|
||||
else
|
||||
merged.Add("destinationLists",
|
||||
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
|
||||
|
||||
|
||||
if (system["cameraLists"] == null)
|
||||
merged.Add("cameraLists", template["cameraLists"]);
|
||||
else
|
||||
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
|
||||
|
||||
if (system["audioControlPointLists"] == null)
|
||||
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
|
||||
else
|
||||
merged.Add("audioControlPointLists",
|
||||
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
|
||||
|
||||
|
||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
||||
// level anyway...
|
||||
if (template["tieLines"] != null)
|
||||
merged.Add("tieLines", template["tieLines"]);
|
||||
else if (system["tieLines"] != null)
|
||||
merged.Add("tieLines", system["tieLines"]);
|
||||
else
|
||||
merged.Add("tieLines", new JArray());
|
||||
|
||||
if (template["joinMaps"] != null)
|
||||
merged.Add("joinMaps", template["joinMaps"]);
|
||||
else
|
||||
merged.Add("joinMaps", new JObject());
|
||||
|
||||
if (system["global"] != null)
|
||||
merged.Add("global", Merge(template["global"], system["global"], "global"));
|
||||
else
|
||||
merged.Add("global", template["global"]);
|
||||
|
||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the contents of a base and a delta array, matching the entries on a top-level property
|
||||
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have
|
||||
/// a matched item in base array will not be merged. Non keyed system items will replace the template items.
|
||||
/// </summary>
|
||||
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path)
|
||||
{
|
||||
var result = new JArray();
|
||||
if (a2 == null || a2.Count == 0) // If the system array is null or empty, return the template array
|
||||
return a1;
|
||||
else if (a1 != null)
|
||||
{
|
||||
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
|
||||
{ // with the system array
|
||||
return a2;
|
||||
}
|
||||
else // The arrays are keyed, merge them by key
|
||||
{
|
||||
for (int i = 0; i < a1.Count(); i++)
|
||||
{
|
||||
var a1Dev = a1[i];
|
||||
// Try to get a system device and if found, merge it onto template
|
||||
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
|
||||
if (a2Match != null)
|
||||
{
|
||||
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
|
||||
result.Add(mergedItem);
|
||||
}
|
||||
else
|
||||
result.Add(a1Dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper for using with JTokens. Converts to JObject
|
||||
/// </summary>
|
||||
static JObject Merge(JToken t1, JToken t2, string path)
|
||||
{
|
||||
return Merge(JObject.FromObject(t1), JObject.FromObject(t2), path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge o2 onto o1
|
||||
/// </summary>
|
||||
/// <param name="o1"></param>
|
||||
/// <param name="o2"></param>
|
||||
/// <param name="path"></param>
|
||||
static JObject Merge(JObject o1, JObject o2, string path)
|
||||
{
|
||||
foreach (var o2Prop in o2)
|
||||
{
|
||||
var propKey = o2Prop.Key;
|
||||
var o1Value = o1[propKey];
|
||||
var o2Value = o2[propKey];
|
||||
|
||||
// if the property doesn't exist on o1, then add it.
|
||||
if (o1Value == null)
|
||||
{
|
||||
o1.Add(propKey, o2Value);
|
||||
}
|
||||
// otherwise merge them
|
||||
else
|
||||
{
|
||||
// Drill down
|
||||
var propPath = String.Format("{0}.{1}", path, propKey);
|
||||
try
|
||||
{
|
||||
|
||||
if (o1Value is JArray)
|
||||
{
|
||||
if (o2Value is JArray)
|
||||
{
|
||||
o1Value.Replace(MergeArraysOnTopLevelProperty(o1Value as JArray, o2Value as JArray, "key", propPath));
|
||||
}
|
||||
}
|
||||
else if (o2Prop.Value.HasValues && o1Value.HasValues)
|
||||
{
|
||||
o1Value.Replace(Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Value), propPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
o1Value.Replace(o2Prop.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o1;
|
||||
Debug.LogMessage(e, "ERROR: Config load failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="doubleConfig"></param>
|
||||
/// <returns></returns>
|
||||
public static JObject MergeConfigs(JObject doubleConfig)
|
||||
{
|
||||
var system = JObject.FromObject(doubleConfig["system"]);
|
||||
var template = JObject.FromObject(doubleConfig["template"]);
|
||||
var merged = new JObject();
|
||||
|
||||
// Put together top-level objects
|
||||
if (system["info"] != null)
|
||||
merged.Add("info", Merge(template["info"], system["info"], "infO"));
|
||||
else
|
||||
merged.Add("info", template["info"]);
|
||||
|
||||
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
|
||||
system["devices"] as JArray, "key", "devices"));
|
||||
|
||||
if (system["rooms"] == null)
|
||||
merged.Add("rooms", template["rooms"]);
|
||||
else
|
||||
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
|
||||
system["rooms"] as JArray, "key", "rooms"));
|
||||
|
||||
if (system["sourceLists"] == null)
|
||||
merged.Add("sourceLists", template["sourceLists"]);
|
||||
else
|
||||
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
|
||||
|
||||
if (system["destinationLists"] == null)
|
||||
merged.Add("destinationLists", template["destinationLists"]);
|
||||
else
|
||||
merged.Add("destinationLists",
|
||||
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
|
||||
|
||||
|
||||
if (system["cameraLists"] == null)
|
||||
merged.Add("cameraLists", template["cameraLists"]);
|
||||
else
|
||||
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
|
||||
|
||||
if (system["audioControlPointLists"] == null)
|
||||
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
|
||||
else
|
||||
merged.Add("audioControlPointLists",
|
||||
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
|
||||
|
||||
|
||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
||||
// level anyway...
|
||||
if (template["tieLines"] != null)
|
||||
merged.Add("tieLines", template["tieLines"]);
|
||||
else if (system["tieLines"] != null)
|
||||
merged.Add("tieLines", system["tieLines"]);
|
||||
else
|
||||
merged.Add("tieLines", new JArray());
|
||||
|
||||
if (template["joinMaps"] != null)
|
||||
merged.Add("joinMaps", template["joinMaps"]);
|
||||
else
|
||||
merged.Add("joinMaps", new JObject());
|
||||
|
||||
if (system["global"] != null)
|
||||
merged.Add("global", Merge(template["global"], system["global"], "global"));
|
||||
else
|
||||
merged.Add("global", template["global"]);
|
||||
|
||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the contents of a base and a delta array, matching the entries on a top-level property
|
||||
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have
|
||||
/// a matched item in base array will not be merged. Non keyed system items will replace the template items.
|
||||
/// </summary>
|
||||
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path)
|
||||
{
|
||||
var result = new JArray();
|
||||
if (a2 == null || a2.Count == 0) // If the system array is null or empty, return the template array
|
||||
return a1;
|
||||
else if (a1 != null)
|
||||
{
|
||||
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
|
||||
{ // with the system array
|
||||
return a2;
|
||||
}
|
||||
else // The arrays are keyed, merge them by key
|
||||
{
|
||||
for (int i = 0; i < a1.Count(); i++)
|
||||
{
|
||||
var a1Dev = a1[i];
|
||||
// Try to get a system device and if found, merge it onto template
|
||||
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
|
||||
if (a2Match != null)
|
||||
{
|
||||
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
|
||||
result.Add(mergedItem);
|
||||
}
|
||||
else
|
||||
result.Add(a1Dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper for using with JTokens. Converts to JObject
|
||||
/// </summary>
|
||||
static JObject Merge(JToken t1, JToken t2, string path)
|
||||
{
|
||||
return Merge(JObject.FromObject(t1), JObject.FromObject(t2), path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge o2 onto o1
|
||||
/// </summary>
|
||||
/// <param name="o1"></param>
|
||||
/// <param name="o2"></param>
|
||||
/// <param name="path"></param>
|
||||
static JObject Merge(JObject o1, JObject o2, string path)
|
||||
{
|
||||
foreach (var o2Prop in o2)
|
||||
{
|
||||
var propKey = o2Prop.Key;
|
||||
var o1Value = o1[propKey];
|
||||
var o2Value = o2[propKey];
|
||||
|
||||
// if the property doesn't exist on o1, then add it.
|
||||
if (o1Value == null)
|
||||
{
|
||||
o1.Add(propKey, o2Value);
|
||||
}
|
||||
// otherwise merge them
|
||||
else
|
||||
{
|
||||
// Drill down
|
||||
var propPath = String.Format("{0}.{1}", path, propKey);
|
||||
try
|
||||
{
|
||||
|
||||
if (o1Value is JArray)
|
||||
{
|
||||
if (o2Value is JArray)
|
||||
{
|
||||
o1Value.Replace(MergeArraysOnTopLevelProperty(o1Value as JArray, o2Value as JArray, "key", propPath));
|
||||
}
|
||||
}
|
||||
else if (o2Prop.Value.HasValues && o1Value.HasValues)
|
||||
{
|
||||
o1Value.Replace(Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Value), propPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
o1Value.Replace(o2Prop.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o1;
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,18 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
namespace PepperDash.Core;
|
||||
|
||||
public class EncodingHelper
|
||||
{
|
||||
public class EncodingHelper
|
||||
public static string ConvertUtf8ToAscii(string utf8String)
|
||||
{
|
||||
public static string ConvertUtf8ToAscii(string utf8String)
|
||||
{
|
||||
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||
}
|
||||
|
||||
public static string ConvertUtf8ToUtf16(string utf8String)
|
||||
{
|
||||
return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||
}
|
||||
|
||||
public static string ConvertUtf8ToUtf16(string utf8String)
|
||||
{
|
||||
return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,30 +6,28 @@ using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique key interface to require a unique key for the class
|
||||
/// </summary>
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Unique key interface to require a unique key for the class
|
||||
/// </summary>
|
||||
public interface IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
[JsonProperty("key")]
|
||||
string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Named Keyed device interface. Forces the device to have a Unique Key and a name.
|
||||
/// <summary>
|
||||
/// Gets the unique key associated with the object.
|
||||
/// </summary>
|
||||
public interface IKeyName : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Isn't it obvious :)
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
string Name { get; }
|
||||
}
|
||||
[JsonProperty("key")]
|
||||
string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Named Keyed device interface. Forces the device to have a Unique Key and a name.
|
||||
/// </summary>
|
||||
public interface IKeyName : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name associated with the current object.
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
string Name { get; }
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
namespace PepperDash.Core;
|
||||
|
||||
//*********************************************************************************************************
|
||||
/// <summary>
|
||||
/// The core event and status-bearing class that most if not all device and connectors can derive from.
|
||||
@@ -11,35 +11,35 @@ namespace PepperDash.Core
|
||||
public class Device : IKeyName
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
public string Key { get; protected set; }
|
||||
/// <summary>
|
||||
/// Name of the devie
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Enabled { get; protected set; }
|
||||
|
||||
///// <summary>
|
||||
///// A place to store reference to the original config object, if any. These values should
|
||||
///// NOT be used as properties on the device as they are all publicly-settable values.
|
||||
///// </summary>
|
||||
//public DeviceConfig Config { get; private set; }
|
||||
///// <summary>
|
||||
///// Helper method to check if Config exists
|
||||
///// </summary>
|
||||
//public bool HasConfig { get { return Config != null; } }
|
||||
///// <summary>
|
||||
///// A place to store reference to the original config object, if any. These values should
|
||||
///// NOT be used as properties on the device as they are all publicly-settable values.
|
||||
///// </summary>
|
||||
//public DeviceConfig Config { get; private set; }
|
||||
///// <summary>
|
||||
///// Helper method to check if Config exists
|
||||
///// </summary>
|
||||
//public bool HasConfig { get { return Config != null; } }
|
||||
|
||||
List<Action> _PreActivationActions;
|
||||
List<Action> _PostActivationActions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
||||
static Device _DefaultDevice = new Device("Default", "Default");
|
||||
|
||||
@@ -54,27 +54,27 @@ namespace PepperDash.Core
|
||||
Name = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with key and name
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <summary>
|
||||
/// Constructor with key and name
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
public Device(string key, string name) : this(key)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
}
|
||||
|
||||
//public Device(DeviceConfig config)
|
||||
// : this(config.Key, config.Name)
|
||||
//{
|
||||
// Config = config;
|
||||
//}
|
||||
//public Device(DeviceConfig config)
|
||||
// : this(config.Key, config.Name)
|
||||
//{
|
||||
// Config = config;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
/// <summary>
|
||||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPreActivationAction(Action act)
|
||||
{
|
||||
if (_PreActivationActions == null)
|
||||
@@ -82,10 +82,10 @@ namespace PepperDash.Core
|
||||
_PreActivationActions.Add(act);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a post activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
/// <summary>
|
||||
/// Adds a post activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPostActivationAction(Action act)
|
||||
{
|
||||
if (_PostActivationActions == null)
|
||||
@@ -93,55 +93,58 @@ namespace PepperDash.Core
|
||||
_PostActivationActions.Add(act);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the preactivation actions
|
||||
/// </summary>
|
||||
public void PreActivate()
|
||||
{
|
||||
if (_PreActivationActions != null)
|
||||
_PreActivationActions.ForEach(a => {
|
||||
/// <summary>
|
||||
/// Executes the preactivation actions
|
||||
/// </summary>
|
||||
public void PreActivate()
|
||||
{
|
||||
if (_PreActivationActions != null)
|
||||
_PreActivationActions.ForEach(a =>
|
||||
{
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
} catch (Exception e)
|
||||
{
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
|
||||
/// all post-activation at end. Classes needing additional logic to
|
||||
/// run should override CustomActivate()
|
||||
/// </summary>
|
||||
public bool Activate()
|
||||
public bool Activate()
|
||||
{
|
||||
//if (_PreActivationActions != null)
|
||||
// _PreActivationActions.ForEach(a => a.Invoke());
|
||||
//if (_PreActivationActions != null)
|
||||
// _PreActivationActions.ForEach(a => a.Invoke());
|
||||
var result = CustomActivate();
|
||||
//if(result && _PostActivationActions != null)
|
||||
// _PostActivationActions.ForEach(a => a.Invoke());
|
||||
return result;
|
||||
//if(result && _PostActivationActions != null)
|
||||
// _PostActivationActions.ForEach(a => a.Invoke());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the postactivation actions
|
||||
/// </summary>
|
||||
public void PostActivate()
|
||||
{
|
||||
if (_PostActivationActions != null)
|
||||
_PostActivationActions.ForEach(a => {
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// Executes the postactivation actions
|
||||
/// </summary>
|
||||
public void PostActivate()
|
||||
{
|
||||
if (_PostActivationActions != null)
|
||||
_PostActivationActions.ForEach(a =>
|
||||
{
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called in between Pre and PostActivationActions when Activate() is called.
|
||||
@@ -158,14 +161,14 @@ namespace PepperDash.Core
|
||||
/// <returns></returns>
|
||||
public virtual bool Deactivate() { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Helper method to check object for bool value false and fire an Action method
|
||||
/// </summary>
|
||||
/// <param name="o">Should be of type bool, others will be ignored</param>
|
||||
@@ -175,5 +178,14 @@ namespace PepperDash.Core
|
||||
if (o is bool && !(bool)o) a();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a string representation of the object, including its key and name.
|
||||
/// </summary>
|
||||
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
||||
/// null or empty, "---" is used in place of the name.</remarks>
|
||||
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<None Include="$(TargetDir)\$(TargetName).$(Version).cpz" Condition="$(ProjectType) == 'Program'">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>content;</PackagePath>
|
||||
</None>
|
||||
<None Include="$(PackageOutputPath)\$(TargetName).$(Version).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||
<Pack>true</Pack>
|
||||
<PackagePath>content;</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="Create CPLZ" AfterTargets="Build; Rebuild" Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||
<Message Text="Creating CPLZ"></Message>
|
||||
<MakeDir Directories="$(PackageOutputPath)" Condition="!Exists($(PackageOutputPath))"></MakeDir>
|
||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(TargetName).$(Version).cplz" Overwrite="true"/>
|
||||
</Target>
|
||||
<Target Name="Clean CPLZ" AfterTargets="AfterClean" Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).cplz"/>
|
||||
</Target>
|
||||
<Target Name="Copy CPZ" AfterTargets="SimplSharpPostProcess" Condition="$(ProjectType) == 'Program'">
|
||||
<Message Text="Copying CPZ"></Message>
|
||||
<Move SourceFiles="$(TargetDir)\$(TargetName).cpz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).cpz"/>
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).cpz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).cpz"/>
|
||||
</Target>
|
||||
<Target Name="Clean CPZ" AfterTargets="AfterClean" Condition="$(ProjectType) == 'Program'">
|
||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).cpz"/>
|
||||
</Target>
|
||||
<Target Name="Copy CLZ" AfterTargets="SimplSharpPostProcess">
|
||||
<Message Text="Copying CLZ"></Message>
|
||||
<Move SourceFiles="$(TargetDir)\$(TargetName).clz" DestinationFiles="$(TargetDir)\$(TargetName).$(Version).clz"/>
|
||||
<Copy SourceFiles="$(TargetDir)\$(TargetName).$(Version).clz" DestinationFiles="$(PackageOutputPath)\$(TargetName).$(Version).clz"/>
|
||||
</Target>
|
||||
<Target Name="Clean CLZ" AfterTargets="AfterClean">
|
||||
<Delete Files="$(PackageOutputPath)\$(TargetName).$(Version).clz"/>
|
||||
</Target>
|
||||
<Target Name="SimplSharpNewtonsoft" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
|
||||
<ItemGroup>
|
||||
<ReferencePath Condition="'%(FileName)' == 'Newtonsoft.Json.Compact'">
|
||||
<Aliases>doNotUse</Aliases>
|
||||
</ReferencePath>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Build Essentials Plugin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
getVersion:
|
||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-getversion.yml@main
|
||||
secrets: inherit
|
||||
build-4Series:
|
||||
uses: PepperDash/workflow-templates/.github/workflows/essentialsplugins-4Series-builds.yml@main
|
||||
secrets: inherit
|
||||
needs: getVersion
|
||||
if: needs.getVersion.outputs.newVersion == 'true'
|
||||
with:
|
||||
newVersion: ${{ needs.getVersion.outputs.newVersion }}
|
||||
version: ${{ needs.getVersion.outputs.version }}
|
||||
tag: ${{ needs.getVersion.outputs.tag }}
|
||||
channel: ${{ needs.getVersion.outputs.channel }}
|
||||
@@ -2,11 +2,11 @@
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to help with accessing values from the CrestronEthernetHelper class
|
||||
/// </summary>
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Class to help with accessing values from the CrestronEthernetHelper class
|
||||
/// </summary>
|
||||
public class EthernetHelper
|
||||
{
|
||||
/// <summary>
|
||||
@@ -113,5 +113,4 @@ namespace PepperDash.Core
|
||||
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||