mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-01-25 10:24:55 +00:00
Compare commits
187 Commits
2.0.0-alph
...
3.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e51ed643c | ||
|
|
135d65c515 | ||
|
|
493ef59497 | ||
|
|
488dda2c0c | ||
|
|
e314f58035 | ||
|
|
3fad74e595 | ||
|
|
a654560506 | ||
|
|
2138a29b31 | ||
|
|
d6f7a12eb9 | ||
|
|
237fff5398 | ||
|
|
b2eab21fbd | ||
|
|
f2545fb1cf | ||
|
|
27072e3475 | ||
|
|
e4755ed9df | ||
|
|
316867caf8 | ||
|
|
d8fd774324 | ||
|
|
e0058d8cfe | ||
|
|
a055d06bc6 | ||
|
|
66cb592c70 | ||
|
|
d53a5607e2 | ||
|
|
34f59f1410 | ||
|
|
261779d4c4 | ||
|
|
30d5e2b081 | ||
|
|
5516ed16c3 | ||
|
|
8108b9dfdb | ||
|
|
fb4f1482c7 | ||
|
|
54dcb5de08 | ||
|
|
4ef481375c | ||
|
|
d8a88b2a07 | ||
|
|
cc724ddf19 | ||
|
|
e29e800d9d | ||
|
|
134e8ba02e | ||
|
|
a83ba444d3 | ||
|
|
f4c5e6fbeb | ||
|
|
35d7994cc8 | ||
|
|
c3e9d654c9 | ||
|
|
f68b1e9e49 | ||
|
|
cd81b8af73 | ||
|
|
cd52c245a6 | ||
|
|
0b60f53d0e | ||
|
|
ffed2dea8a | ||
|
|
936dce2046 | ||
|
|
b33704eabe | ||
|
|
aca6fe9af5 | ||
|
|
332faaa9cc | ||
|
|
4449077a39 | ||
|
|
86ba9e0f16 | ||
|
|
db2d8a213d | ||
|
|
590e16298c | ||
|
|
1a11e9019c | ||
|
|
0e16dff90c | ||
|
|
d11827bc7b | ||
|
|
631dd2b00d | ||
|
|
639cd2abfb | ||
|
|
f04f70495f | ||
|
|
fa38e8a9a8 | ||
|
|
f351c036ed | ||
|
|
0a7da79356 | ||
|
|
82ebf45921 | ||
|
|
d0dbe986f3 | ||
|
|
aa503f3b29 | ||
|
|
90251d92df | ||
|
|
778af651d0 | ||
|
|
2e31719a84 | ||
|
|
a96fb21898 | ||
|
|
3f45372e1e | ||
|
|
60dbaf547d | ||
|
|
941e537d53 | ||
|
|
f7c5e18af8 | ||
|
|
ab73bbf979 | ||
|
|
1fb1947158 | ||
|
|
e374f7b50f | ||
|
|
7a263a644a | ||
|
|
97bd30e9c9 | ||
|
|
63c653b21f | ||
|
|
c56841d95b | ||
|
|
64d6df70b0 | ||
|
|
d970d806c9 | ||
|
|
5a9b876d80 | ||
|
|
fb60683af6 | ||
|
|
b63996b9e6 | ||
|
|
bc217a2008 | ||
|
|
fec6b0d385 | ||
|
|
f3b4c0aa02 | ||
|
|
a3351812cd | ||
|
|
71815eff17 | ||
|
|
c7f4bf1fb2 | ||
|
|
bec3ab8e73 | ||
|
|
c499d2a2eb | ||
|
|
d9721a362e | ||
|
|
5fb6f3e117 | ||
|
|
c2fb44a662 | ||
|
|
0f9bddf4dd | ||
|
|
ddc2491664 | ||
|
|
9a6209f50a | ||
|
|
49852d25e8 | ||
|
|
ba4ca20936 | ||
|
|
c50726f813 | ||
|
|
0aafe8a62e | ||
|
|
71005940ac | ||
|
|
e5e79316a6 | ||
|
|
5aa1f85df5 | ||
|
|
7bac65002d | ||
|
|
ed0141a536 | ||
|
|
25ebcdfb5d | ||
|
|
b326ccf6c3 | ||
|
|
3a56e47c48 | ||
|
|
171bd6b1ec | ||
|
|
e61fd7777a | ||
|
|
027bdd5bf4 | ||
|
|
b876b8123d | ||
|
|
6e05653c6c | ||
|
|
16d32bc720 | ||
|
|
0bef5d4b77 | ||
|
|
a3c1572a77 | ||
|
|
cc06c7bfd8 | ||
|
|
00a7b25026 | ||
|
|
edc916c9d3 | ||
|
|
c755ecb16c | ||
|
|
a8c36ba243 | ||
|
|
f630d3f410 | ||
|
|
35e0662b27 | ||
|
|
effefc939c | ||
|
|
5afdc2effa | ||
|
|
448cc273ec | ||
|
|
0a2aaa693f | ||
|
|
1ebee58ad6 | ||
|
|
3c5fe88e5a | ||
|
|
8255328be1 | ||
|
|
4bf026601f | ||
|
|
888f1b3888 | ||
|
|
19bd5723c8 | ||
|
|
e3e7add5b9 | ||
|
|
dd66de0463 | ||
|
|
3823943cd9 | ||
|
|
577e111f26 | ||
|
|
528fff569d | ||
|
|
06a6b1caa2 | ||
|
|
621d848418 | ||
|
|
2f9038a501 | ||
|
|
983b18d25a | ||
|
|
048004d441 | ||
|
|
e7e448f02c | ||
|
|
2e61d8d709 | ||
|
|
d8d2c5b340 | ||
|
|
7e736ae519 | ||
|
|
0067e11d3d | ||
|
|
7942c91f73 | ||
|
|
e1638762a1 | ||
|
|
3566400379 | ||
|
|
dde85f39a2 | ||
|
|
735433f660 | ||
|
|
734149960b | ||
|
|
cb16f2a505 | ||
|
|
a95d44e405 | ||
|
|
5820c9d282 | ||
|
|
4d19ecde00 | ||
|
|
826b7fd6d5 | ||
|
|
cb9eb5dafa | ||
|
|
eb955aa014 | ||
|
|
2d9ffca78e | ||
|
|
98f1a09c25 | ||
|
|
a11ad421f0 | ||
|
|
8878ff7ddd | ||
|
|
7e4b5f984f | ||
|
|
64ab315142 | ||
|
|
c47a93f4d0 | ||
|
|
5a55a701d6 | ||
|
|
01862ab9aa | ||
|
|
8ec6fa785e | ||
|
|
e37c675da1 | ||
|
|
3ee8cb7ea3 | ||
|
|
2b6f79b68f | ||
|
|
65369606a4 | ||
|
|
e9954b3081 | ||
|
|
bc67a4382b | ||
|
|
e1b50649fd | ||
|
|
5e69ea1947 | ||
|
|
80b8cc6385 | ||
|
|
82f78bf068 | ||
|
|
d8d27949c3 | ||
|
|
fd1b92a6c0 | ||
|
|
fa9b493431 | ||
|
|
06d806687d | ||
|
|
d8e2f8cd51 | ||
|
|
e5d5c90aa9 | ||
|
|
661f7b827a |
94
.github/workflows/docker.yml
vendored
94
.github/workflows/docker.yml
vendored
@@ -1,94 +0,0 @@
|
||||
name: Branch Build Using Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feature-2.0.0/*
|
||||
- hotfix-2.0.0/*
|
||||
- release-2.0.0/*
|
||||
- development-2.0.0
|
||||
|
||||
env:
|
||||
# solution path doesn't need slashes unless there it is multiple folders deep
|
||||
# solution name does not include extension. .sln is assumed
|
||||
SOLUTION_PATH: .
|
||||
SOLUTION_FILE: PepperDash.Essentials
|
||||
# Do not edit this, we're just creating it here
|
||||
VERSION: 0.0.0-buildtype-buildnumber
|
||||
# Defaults to debug for build type
|
||||
BUILD_TYPE: Debug
|
||||
# Defaults to main as the release branch. Change as necessary
|
||||
RELEASE_BRANCH: main
|
||||
jobs:
|
||||
Build_Project_4-Series:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set Version Number
|
||||
id: setVersion
|
||||
shell: powershell
|
||||
run: |
|
||||
$latestVersion = [version]"2.0.0"
|
||||
|
||||
$newVersion = [version]$latestVersion
|
||||
$phase = ""
|
||||
$newVersionString = ""
|
||||
|
||||
switch -regex ($Env:GITHUB_REF) {
|
||||
'^refs\/pull\/*.' {
|
||||
$phase = 'beta';
|
||||
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/hotfix-2.0.0\/*.' {
|
||||
$phase = 'hotfix'
|
||||
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/release-2.0.0\/*.' {
|
||||
$splitRef = $Env:GITHUB_REF -split "/"
|
||||
$version = [version]($splitRef[-1] -replace "v", "")
|
||||
$phase = 'rc'
|
||||
$newVersionString = "{0}-{1}-{2}" -f $version, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'^refs\/heads\/feature-2.0.0\/*.' {
|
||||
$phase = 'alpha'
|
||||
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
'development-2.0.0' {
|
||||
$phase = 'beta'
|
||||
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
|
||||
}
|
||||
}
|
||||
echo "version=$newVersionString" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
- name: Setup MS Build
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
- name: restore Nuget Packages
|
||||
run: nuget restore .\$($Env:SOLUTION_FILE).sln
|
||||
# Build the solutions in the docker image
|
||||
- name: Build Solution
|
||||
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
|
||||
- name: Create tag for non-rc builds
|
||||
if: contains(steps.setVersion.outputs.version, 'alpha')
|
||||
run: |
|
||||
git tag ${{ steps.setVersion.outputs.version }}
|
||||
git push --tags origin
|
||||
# Create the release on the source repo
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
# if: contains(steps.setVersion.outputs.version,'-rc-') ||
|
||||
# contains(steps.setVersion.outputs.version,'-hotfix-') ||
|
||||
# contains(steps.setVersion.outputs.version, '-beta-')
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: 'output\**\*.*(cpz|cplz)'
|
||||
generateReleaseNotes: true
|
||||
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
|
||||
tag: ${{ steps.setVersion.outputs.version }}
|
||||
- name: Setup Nuget
|
||||
run: |
|
||||
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
|
||||
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
|
||||
- name: Publish to Nuget
|
||||
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||
- name: Publish to Github Nuget
|
||||
run: nuget push .\output\*.nupkg -Source github
|
||||
247
.github/workflows/essentials-3-dev-build.yml
vendored
Normal file
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 "=================================="
|
||||
55
.github/workflows/main.yml
vendored
55
.github/workflows/main.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: main Build using Docker
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
branches:
|
||||
- main-2.0.0
|
||||
env:
|
||||
# solution path doesn't need slashes unless there it is multiple folders deep
|
||||
# solution name does not include extension. .sln is assumed
|
||||
SOLUTION_PATH: PepperDashEssentials
|
||||
SOLUTION_FILE: PepperDashEssentials
|
||||
# Do not edit this, we're just creating it here
|
||||
VERSION: 0.0.0-buildtype-buildnumber
|
||||
# Defaults to debug for build type
|
||||
BUILD_TYPE: Release
|
||||
# Defaults to main as the release branch. Change as necessary
|
||||
RELEASE_BRANCH: main
|
||||
jobs:
|
||||
Build_Project:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
# First we checkout the source repo
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
# Generate the appropriate version number
|
||||
- name: Set Version Number
|
||||
shell: powershell
|
||||
id: setVersion
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: echo "VERSION=$($Env:TAG_NAME)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
- name: Setup MS Build
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
- name: restore Nuget Packages
|
||||
run: nuget restore .\$($Env:SOLUTION_FILE).sln
|
||||
- name: Build Solution
|
||||
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
|
||||
- name: Upload Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
updateRelease: true
|
||||
artifacts: 'output\**\*.*(cpz|cplz)'
|
||||
tag: ${{ steps.setVersion.outputs.version }}
|
||||
- name: Setup Nuget
|
||||
run: |
|
||||
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
|
||||
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
|
||||
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
|
||||
- name: Publish to Nuget
|
||||
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
|
||||
- name: Publish to Github Nuget
|
||||
run: nuget push .\output\*.nupkg -Source github
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -391,3 +391,4 @@ FodyWeavers.xsd
|
||||
essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/PepperDash_Essentials_Interfaces.csproj
|
||||
.DS_Store
|
||||
/._PepperDash.Essentials.sln
|
||||
.vscode/settings.json
|
||||
|
||||
7
runtimeconfig.json
Normal file
7
runtimeconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"configProperties": {
|
||||
"System.Globalization.Invariant": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<MakeDir Directories="$(PackageOutputPath)\$(AssemblyName)" Condition="!Exists('$(PackageOutputPath)\$(AssemblyName)')" />
|
||||
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
|
||||
</Target>
|
||||
<Target Name="Copy CPZ NET6" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' == 'net6.0' ) Or ( '$(TargetFramework)' == 'net8.0' ))">
|
||||
<Target Name="Copy CPZ NET6" AfterTargets="SimplSharpPostProcess" Condition="$(ProjectType) == 'Program' And ( ('$(TargetFramework)' == 'net6.0') Or ('$(TargetFramework)' == 'net8.0') )">
|
||||
<Message Text="Copying CPZ"></Message>
|
||||
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cpz" />
|
||||
</Target>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.EthernetCommunication;
|
||||
@@ -18,81 +18,6 @@ using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for bridges
|
||||
/// </summary>
|
||||
public static class BridgeHelper
|
||||
{
|
||||
public static void PrintJoinMap(string command)
|
||||
{
|
||||
var targets = command.Split(' ');
|
||||
|
||||
var bridgeKey = targets[0].Trim();
|
||||
|
||||
var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced;
|
||||
|
||||
if (bridge == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
var deviceKey = targets[1].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(deviceKey)) return;
|
||||
bridge.PrintJoinMapForDevice(deviceKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.PrintJoinMaps();
|
||||
}
|
||||
}
|
||||
public static void JoinmapMarkdown(string command)
|
||||
{
|
||||
var targets = command.Split(' ');
|
||||
|
||||
var bridgeKey = targets[0].Trim();
|
||||
|
||||
var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced;
|
||||
|
||||
if (bridge == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
var deviceKey = targets[1].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(deviceKey)) return;
|
||||
bridge.MarkdownJoinMapForDevice(deviceKey, bridgeKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.MarkdownForBridge(bridgeKey);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all bridge class variants
|
||||
/// </summary>
|
||||
public class BridgeBase : EssentialsDevice
|
||||
{
|
||||
public BridgeApi Api { get; protected set; }
|
||||
|
||||
public BridgeBase(string key) :
|
||||
base(key)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for bridge API variants
|
||||
/// </summary>
|
||||
@@ -168,19 +93,15 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Linking Device: '{0}'", device.Key);
|
||||
|
||||
if (!typeof(IBridgeAdvanced).IsAssignableFrom(device.GetType().GetCType()))
|
||||
if (device is IBridgeAdvanced bridge)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this,
|
||||
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
|
||||
device.Key);
|
||||
bridge.LinkToApi(Eisc, d.JoinStart, d.JoinMapKey, this);
|
||||
continue;
|
||||
}
|
||||
|
||||
var bridge = device as IBridgeAdvanced;
|
||||
if (bridge != null)
|
||||
{
|
||||
bridge.LinkToApi(Eisc, d.JoinStart, d.JoinMapKey, this);
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Information, this,
|
||||
"{0} is not compatible with this bridge type. Please use 'eiscapi' instead, or updae the device.",
|
||||
device.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,11 +170,11 @@ namespace PepperDash.Essentials.Core.Bridges
|
||||
/// </summary>
|
||||
public virtual void PrintJoinMaps()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Join Maps for EISC IPID: {0}", Eisc.ID.ToString("X"));
|
||||
CrestronConsole.ConsoleCommandResponse("Join Maps for EISC IPID: {0}\r\n", Eisc.ID.ToString("X"));
|
||||
|
||||
foreach (var joinMap in JoinMaps)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Join map for device '{0}':", joinMap.Key);
|
||||
CrestronConsole.ConsoleCommandResponse("Join map for device '{0}':", joinMap.Key);
|
||||
joinMap.Value.PrintJoinMapInfo();
|
||||
}
|
||||
}
|
||||
|
||||
66
src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs
Normal file
66
src/PepperDash.Essentials.Core/Bridges/BridgeHelper.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
//using PepperDash.Essentials.Devices.Common.Cameras;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Bridges
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for bridges
|
||||
/// </summary>
|
||||
public static class BridgeHelper
|
||||
{
|
||||
public static void PrintJoinMap(string command)
|
||||
{
|
||||
var targets = command.Split(' ');
|
||||
|
||||
var bridgeKey = targets[0].Trim();
|
||||
|
||||
if (!(DeviceManager.GetDeviceForKey(bridgeKey) is EiscApiAdvanced bridge))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
var deviceKey = targets[1].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(deviceKey)) return;
|
||||
bridge.PrintJoinMapForDevice(deviceKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.PrintJoinMaps();
|
||||
}
|
||||
}
|
||||
public static void JoinmapMarkdown(string command)
|
||||
{
|
||||
var targets = command.Split(' ');
|
||||
|
||||
var bridgeKey = targets[0].Trim();
|
||||
|
||||
var bridge = DeviceManager.GetDeviceForKey(bridgeKey) as EiscApiAdvanced;
|
||||
|
||||
if (bridge == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to find advanced bridge with key: '{0}'", bridgeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
var deviceKey = targets[1].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(deviceKey)) return;
|
||||
bridge.MarkdownJoinMapForDevice(deviceKey, bridgeKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.MarkdownForBridge(bridgeKey);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,6 +93,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
void OnDataReceived(string s)
|
||||
{
|
||||
var eventSubscribed = false;
|
||||
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
@@ -101,6 +102,7 @@ namespace PepperDash.Essentials.Core
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", ComTextHelper.GetEscapedText(bytes));
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
eventSubscribed = true;
|
||||
}
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
@@ -108,7 +110,10 @@ namespace PepperDash.Essentials.Core
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Received: '{0}'", s);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(s));
|
||||
eventSubscribed = true;
|
||||
}
|
||||
|
||||
if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
|
||||
}
|
||||
|
||||
public override bool Deactivate()
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (objectType == typeof(ComPort.ComPortSpec))
|
||||
if (objectType == typeof(ComPort.ComPortSpec?))
|
||||
{
|
||||
var newSer = new JsonSerializer();
|
||||
newSer.Converters.Add(new ComSpecPropsJsonConverter());
|
||||
newSer.ObjectCreationHandling = ObjectCreationHandling.Replace;
|
||||
return newSer.Deserialize<ComPort.ComPortSpec>(reader);
|
||||
return newSer.Deserialize<ComPort.ComPortSpec?>(reader);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(ComPort.ComPortSpec);
|
||||
return objectType == typeof(ComPort.ComPortSpec?);
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace PepperDash.Essentials.Core
|
||||
switch (controlConfig.Method)
|
||||
{
|
||||
case eControlMethod.Com:
|
||||
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams, controlConfig);
|
||||
comm = new ComPortController(deviceConfig.Key + "-com", GetComPort, controlConfig.ComParams.Value, controlConfig);
|
||||
break;
|
||||
case eControlMethod.Cec:
|
||||
comm = new CecPortController(deviceConfig.Key + "-cec", GetCecPort, controlConfig);
|
||||
@@ -115,7 +115,7 @@ namespace PepperDash.Essentials.Core
|
||||
var comPar = config.ComParams;
|
||||
var dev = GetIComPortsDeviceFromManagedDevice(config.ControlPortDevKey);
|
||||
if (dev != null && config.ControlPortNumber <= dev.NumberOfComPorts)
|
||||
return dev.ComPorts[config.ControlPortNumber];
|
||||
return dev.ComPorts[config.ControlPortNumber.Value];
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetComPort: Device '{0}' does not have com port {1}", config.ControlPortDevKey, config.ControlPortNumber);
|
||||
return null;
|
||||
}
|
||||
@@ -201,23 +201,26 @@ namespace PepperDash.Essentials.Core
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class EssentialsControlPropertiesConfig :
|
||||
PepperDash.Core.ControlPropertiesConfig
|
||||
public class EssentialsControlPropertiesConfig :
|
||||
ControlPropertiesConfig
|
||||
{
|
||||
|
||||
[JsonProperty("comParams", NullValueHandling = NullValueHandling.Ignore)]
|
||||
[JsonConverter(typeof(ComSpecJsonConverter))]
|
||||
public ComPort.ComPortSpec ComParams { get; set; }
|
||||
public ComPort.ComPortSpec? ComParams { get; set; }
|
||||
|
||||
public string CresnetId { get; set; }
|
||||
[JsonProperty("cresnetId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string CresnetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to provide uint conversion of string CresnetId
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint CresnetIdInt
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
return Convert.ToUInt32(CresnetId, 16);
|
||||
}
|
||||
@@ -228,11 +231,13 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("infinetId", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string InfinetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attepmts to provide uiont conversion of string InifinetId
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint InfinetIdInt
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.DebugThings;
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
|
||||
[Obsolete("Please use the builtin HttpClient class instead: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines")]
|
||||
public class GenericHttpClient : Device, IBasicCommunication
|
||||
{
|
||||
public HttpClient Client;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using Crestron.SimplSharpPro;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
public class AudioControlPointListItem
|
||||
{
|
||||
[JsonProperty("levelControls")]
|
||||
public Dictionary<string, LevelControlListItem> LevelControls { get; set; } = new Dictionary<string, LevelControlListItem>();
|
||||
|
||||
[JsonProperty("presets")]
|
||||
public Dictionary<string, PresetListItem> Presets { get; set; } = new Dictionary<string, PresetListItem>();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Essentials.Core.Devices;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
@@ -23,7 +24,13 @@ namespace PepperDash.Essentials.Core.Config
|
||||
public Dictionary<string, Dictionary<string, SourceListItem>> SourceLists { get; set; }
|
||||
|
||||
[JsonProperty("destinationLists")]
|
||||
public Dictionary<string, Dictionary<string,DestinationListItem>> DestinationLists { get; set; }
|
||||
public Dictionary<string, Dictionary<string, DestinationListItem>> DestinationLists { get; set; }
|
||||
|
||||
[JsonProperty("audioControlPointLists")]
|
||||
public Dictionary<string, AudioControlPointListItem> AudioControlPointLists { get; set; }
|
||||
|
||||
[JsonProperty("cameraLists")]
|
||||
public Dictionary<string, Dictionary<string, CameraListItem>> CameraLists { get; set; }
|
||||
|
||||
[JsonProperty("tieLines")]
|
||||
public List<TieLineConfig> TieLines { get; set; }
|
||||
@@ -37,6 +44,8 @@ namespace PepperDash.Essentials.Core.Config
|
||||
Devices = new List<DeviceConfig>();
|
||||
SourceLists = new Dictionary<string, Dictionary<string, SourceListItem>>();
|
||||
DestinationLists = new Dictionary<string, Dictionary<string, DestinationListItem>>();
|
||||
AudioControlPointLists = new Dictionary<string, AudioControlPointListItem>();
|
||||
CameraLists = new Dictionary<string, Dictionary<string, CameraListItem>>();
|
||||
TieLines = new List<TieLineConfig>();
|
||||
JoinMaps = new Dictionary<string, JObject>();
|
||||
}
|
||||
@@ -46,7 +55,7 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// </summary>
|
||||
public Dictionary<string, SourceListItem> GetSourceListForKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || !SourceLists.ContainsKey(key))
|
||||
if (SourceLists == null || string.IsNullOrEmpty(key) || !SourceLists.ContainsKey(key))
|
||||
return null;
|
||||
|
||||
return SourceLists[key];
|
||||
@@ -55,17 +64,41 @@ namespace PepperDash.Essentials.Core.Config
|
||||
/// <summary>
|
||||
/// Retrieves a DestinationListItem based on the key
|
||||
/// </summary>
|
||||
/// <param name="key">key of the item to retrieve</param>
|
||||
/// <returns>DestinationListItem if the key exists, null otherwise</returns>
|
||||
/// <param name="key">key of the list to retrieve</param>
|
||||
/// <returns>DestinationList if the key exists, null otherwise</returns>
|
||||
public Dictionary<string, DestinationListItem> GetDestinationListForKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || !DestinationLists.ContainsKey(key))
|
||||
if (DestinationLists == null || string.IsNullOrEmpty(key) || !DestinationLists.ContainsKey(key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return DestinationLists[key];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a AudioControlPointList based on the key
|
||||
/// </summary>
|
||||
/// <param name="key">key of the list to retrieve</param>
|
||||
/// <returns>AudioControlPointList if the key exists, null otherwise</returns>
|
||||
public AudioControlPointListItem GetAudioControlPointListForKey(string key)
|
||||
{
|
||||
if (AudioControlPointLists == null || string.IsNullOrEmpty(key) || !AudioControlPointLists.ContainsKey(key))
|
||||
return null;
|
||||
|
||||
return AudioControlPointLists[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks CameraLists for a given list and returns it if found. Otherwise, returns null
|
||||
/// </summary>
|
||||
public Dictionary<string, CameraListItem> GetCameraListForKey(string key)
|
||||
{
|
||||
if (CameraLists == null || string.IsNullOrEmpty(key) || !CameraLists.ContainsKey(key))
|
||||
return null;
|
||||
|
||||
return CameraLists[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks Devices for an item with a Key that matches and returns it if found. Otherwise, retunes null
|
||||
|
||||
@@ -31,9 +31,17 @@ namespace PepperDash.Essentials.Core.Config
|
||||
if (string.IsNullOrEmpty(SystemUrl))
|
||||
return "missing url";
|
||||
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
if (SystemUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
} else
|
||||
{
|
||||
var result = Regex.Match(SystemUrl, @"https?:\/\/.*\/systems\/(.*)\/.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +52,18 @@ namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
if (string.IsNullOrEmpty(TemplateUrl))
|
||||
return "missing template url";
|
||||
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
|
||||
if (TemplateUrl.Contains("#"))
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/templates\/(.*)\/#.*");
|
||||
string uuid = result.Groups[1].Value;
|
||||
return uuid;
|
||||
} else
|
||||
{
|
||||
var result = Regex.Match(TemplateUrl, @"https?:\/\/.*\/system-templates\/(.*)\/system-template-versions\/(.*)\/.*");
|
||||
string uuid = result.Groups[2].Value;
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the info section of a Config file
|
||||
/// </summary>
|
||||
public class InfoConfig
|
||||
/// <summary>
|
||||
/// Represents the info section of a Config file
|
||||
/// </summary>
|
||||
public class InfoConfig
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
/// <summary>
|
||||
/// Represents a generic digital input deviced tied to a versiport
|
||||
/// </summary>
|
||||
public class GenericVersiportDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput
|
||||
public class GenericVersiportDigitalInputDevice : EssentialsBridgeableDevice, IDigitalInput, IPartitionStateProvider
|
||||
{
|
||||
public Versiport InputPort { get; private set; }
|
||||
|
||||
@@ -35,10 +35,15 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
}
|
||||
}
|
||||
|
||||
public BoolFeedback PartitionPresentFeedback { get; }
|
||||
|
||||
public bool PartitionPresent => !InputStateFeedbackFunc();
|
||||
|
||||
public GenericVersiportDigitalInputDevice(string key, string name, Func<IOPortConfig, Versiport> postActivationFunc, IOPortConfig config) :
|
||||
base(key, name)
|
||||
{
|
||||
InputStateFeedback = new BoolFeedback(InputStateFeedbackFunc);
|
||||
PartitionPresentFeedback = new BoolFeedback(() => !InputStateFeedbackFunc());
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
@@ -52,7 +57,8 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
|
||||
InputPort.VersiportChange += InputPort_VersiportChange;
|
||||
|
||||
|
||||
InputStateFeedback.FireUpdate();
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Created GenericVersiportDigitalInputDevice on port '{0}'. DisablePullUpResistor: '{1}'", config.PortNumber, InputPort.DisablePullUpResistor);
|
||||
|
||||
@@ -65,7 +71,10 @@ namespace PepperDash.Essentials.Core.CrestronIO
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Versiport change: {0}", args.Event);
|
||||
|
||||
if(args.Event == eVersiportEvent.DigitalInChange)
|
||||
{
|
||||
InputStateFeedback.FireUpdate();
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
public interface IEmergencyOSD
|
||||
{
|
||||
void ShowEmergencyMessage(string url);
|
||||
void HideEmergencyMessage();
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,22 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
/// <example>
|
||||
/// See MockDisplay for example implemntation
|
||||
/// </example>
|
||||
public interface IHasInputs<TKey, TSelector>: IKeyName
|
||||
[Obsolete("Use IHasInputs<T> instead. Will be removed for 2.0 release")]
|
||||
public interface IHasInputs<T, TSelector>: IKeyName
|
||||
{
|
||||
ISelectableItems<TKey> Inputs { get; }
|
||||
ISelectableItems<T> Inputs { get; }
|
||||
}
|
||||
|
||||
void SetInput(TSelector selector);
|
||||
|
||||
/// <summary>
|
||||
/// Describes a device that has selectable inputs
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type to use as the key for each input item. Most likely an enum or string</typeparam>\
|
||||
/// <example>
|
||||
/// See MockDisplay for example implemntation
|
||||
/// </example>
|
||||
public interface IHasInputs<T> : IKeyName
|
||||
{
|
||||
ISelectableItems<T> Inputs { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
public interface IHumiditySensor
|
||||
{
|
||||
/// <summary>
|
||||
/// Reports the relative humidity level. Level ranging from 0 to 100 (for 0% to 100%
|
||||
/// RH). EventIds: HumidityFeedbackFeedbackEventId will trigger to indicate change.
|
||||
/// </summary>
|
||||
IntFeedback HumidityFeedback { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,10 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
void AddDeviceMessenger(IMobileControlMessenger messenger);
|
||||
|
||||
bool CheckForDeviceMessenger(string key);
|
||||
}
|
||||
|
||||
IMobileControlRoomMessenger GetRoomMessenger(string key);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a mobile control messenger
|
||||
@@ -106,4 +109,11 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
Action<string,string, JToken> Action { get; }
|
||||
}
|
||||
|
||||
public interface IMobileControlTouchpanelController : IKeyed
|
||||
{
|
||||
string DefaultRoomKey { get; }
|
||||
void SetAppUrl(string url);
|
||||
bool UseDirectServer { get; }
|
||||
bool ZoomRoomController { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a class that has warm up and cool down
|
||||
/// </summary>
|
||||
public interface IProjectorScreenLiftControl
|
||||
{
|
||||
void Raise();
|
||||
void Lower();
|
||||
BoolFeedback IsInUpPosition { get; }
|
||||
bool InUpPosition { get; }
|
||||
event EventHandler<EventArgs> PositionChanged;
|
||||
string DisplayDeviceKey { get; }
|
||||
eScreenLiftControlType Type { get; } // screen/lift
|
||||
}
|
||||
|
||||
public enum eScreenLiftControlType
|
||||
{
|
||||
lift,
|
||||
screen
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,6 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
Dictionary<TKey, ISelectableItem> Items { get; set; }
|
||||
|
||||
[JsonProperty("currentItem")]
|
||||
string CurrentItem { get; set; }
|
||||
TKey CurrentItem { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||
{
|
||||
public interface ITemperatureSensor
|
||||
{
|
||||
/// <summary>
|
||||
/// The values will range from -400 to +1760 (for -40° to +176° F) or -400 to +800
|
||||
/// (for -40° to +80° C)in tenths of a degree.
|
||||
/// </summary>
|
||||
IntFeedback TemperatureFeedback { get; }
|
||||
|
||||
|
||||
BoolFeedback TemperatureInCFeedback { get; }
|
||||
|
||||
void SetTemperatureFormat(bool setToC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public abstract class AudioControlListItemBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Key of the parent device in the DeviceManager
|
||||
/// </summary>
|
||||
[JsonProperty("parentDeviceKey")]
|
||||
public string ParentDeviceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional key of the item in the parent device
|
||||
/// </summary>
|
||||
[JsonProperty("itemKey")]
|
||||
public string ItemKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A name that will override the items's name on the UI
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the item should be included in the user accessible list
|
||||
/// </summary>
|
||||
[JsonProperty("includeInUserList")]
|
||||
public bool IncludeInUserList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to specify the order of the items in the source list when displayed
|
||||
/// </summary>
|
||||
[JsonProperty("order")]
|
||||
public int Order { get; set; }
|
||||
}
|
||||
}
|
||||
76
src/PepperDash.Essentials.Core/Devices/CameraListItem.cs
Normal file
76
src/PepperDash.Essentials.Core/Devices/CameraListItem.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class CameraListItem
|
||||
{
|
||||
[JsonProperty("deviceKey")]
|
||||
public string DeviceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source Device for this, if it exists in DeviceManager
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Device CameraDevice
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cameraDevice == null)
|
||||
_cameraDevice = DeviceManager.GetDeviceForKey(DeviceKey) as Device;
|
||||
return _cameraDevice;
|
||||
}
|
||||
}
|
||||
Device _cameraDevice;
|
||||
|
||||
/// <summary>
|
||||
/// Gets either the source's Name or this AlternateName property, if
|
||||
/// defined. If source doesn't exist, returns "Missing source"
|
||||
/// </summary>
|
||||
[JsonProperty("preferredName")]
|
||||
public string PreferredName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
if (CameraDevice == null)
|
||||
return "---";
|
||||
return CameraDevice.Name;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A name that will override the source's name on the UI
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Specifies and icon for the source list item
|
||||
/// </summary>
|
||||
[JsonProperty("icon")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alternate icon
|
||||
/// </summary>
|
||||
[JsonProperty("altIcon", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string AltIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the item should be included in the user facing list
|
||||
/// </summary>
|
||||
[JsonProperty("includeInUserList")]
|
||||
public bool IncludeInUserList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to specify the order of the items in the source list when displayed
|
||||
/// </summary>
|
||||
[JsonProperty("order")]
|
||||
public int Order { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -52,5 +52,11 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
[JsonProperty("sinkType")]
|
||||
public eRoutingSignalType SinkType { get; set; }
|
||||
|
||||
[JsonProperty("isCodecContentDestination")]
|
||||
public bool isCodecContentDestination { get; set; }
|
||||
|
||||
[JsonProperty("isProgramAudioDestination")]
|
||||
public bool isProgramAudioDestination { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,131 +1,203 @@
|
||||
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class DeviceJsonApi
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
public static void DoDeviceActionWithJson(string json)
|
||||
{
|
||||
if (String.IsNullOrEmpty(json))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
"Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var action = JsonConvert.DeserializeObject<DeviceActionWrapper>(json);
|
||||
public class DeviceJsonApi
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
public static void DoDeviceActionWithJson(string json)
|
||||
{
|
||||
if (String.IsNullOrEmpty(json))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
"Please provide a JSON object matching the format {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}.\r\nIf the method has no parameters, the \"params\" object may be omitted.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var action = JsonConvert.DeserializeObject<DeviceActionWrapper>(json);
|
||||
|
||||
DoDeviceAction(action);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}");
|
||||
}
|
||||
|
||||
}
|
||||
DoDeviceAction(action);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Incorrect format for JSON. Please check that the format matches {\"deviceKey\":\"myDevice\", \"methodName\":\"someMethod\", \"params\": [\"param1\", true]}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public static void DoDeviceAction(DeviceActionWrapper action)
|
||||
{
|
||||
var key = action.DeviceKey;
|
||||
var obj = FindObjectOnPath(key);
|
||||
if (obj == null)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key);
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public static void DoDeviceAction(DeviceActionWrapper action)
|
||||
{
|
||||
var key = action.DeviceKey;
|
||||
var obj = FindObjectOnPath(key);
|
||||
if (obj == null)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Unable to find object at path {0}", key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.Params == null)
|
||||
{
|
||||
if (action.Params == null)
|
||||
{
|
||||
//no params, so setting action.Params to empty array
|
||||
action.Params = new object[0];
|
||||
}
|
||||
action.Params = new object[0];
|
||||
}
|
||||
|
||||
CType t = obj.GetType();
|
||||
try
|
||||
{
|
||||
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
|
||||
Type t = obj.GetType();
|
||||
try
|
||||
{
|
||||
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
|
||||
|
||||
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
|
||||
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
"Unable to find method with name {0} and that matches parameters {1}", action.MethodName,
|
||||
action.Params);
|
||||
return;
|
||||
}
|
||||
if (method == null)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
"Unable to find method with name {0} and that matches parameters {1}", action.MethodName,
|
||||
action.Params);
|
||||
return;
|
||||
}
|
||||
var mParams = method.GetParameters();
|
||||
|
||||
var convertedParams = mParams
|
||||
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
|
||||
.ToArray();
|
||||
method.Invoke(obj, convertedParams);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
|
||||
action.DeviceKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName,
|
||||
ex.Message);}
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
|
||||
method.Invoke(obj, convertedParams);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
|
||||
}
|
||||
});
|
||||
|
||||
private static object ConvertType(object value, Type conversionType)
|
||||
{
|
||||
if (!conversionType.IsEnum)
|
||||
{
|
||||
return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
CrestronConsole.ConsoleCommandResponse("Method {0} successfully called on device {1}", method.Name,
|
||||
action.DeviceKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Unable to call method with name {0}. {1}", action.MethodName,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
var stringValue = Convert.ToString(value);
|
||||
public static async Task DoDeviceActionAsync(DeviceActionWrapper action)
|
||||
{
|
||||
var key = action.DeviceKey;
|
||||
var obj = FindObjectOnPath(key);
|
||||
if (obj == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Unable to find object at path {deviceKey}", null, key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
String.Format("{0} cannot be converted to a string prior to conversion to enum"));
|
||||
}
|
||||
return Enum.Parse(conversionType, stringValue, true);
|
||||
}
|
||||
if (action.Params == null)
|
||||
{
|
||||
//no params, so setting action.Params to empty array
|
||||
action.Params = new object[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the properties on a device
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetProperties(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
Type t = obj.GetType();
|
||||
try
|
||||
{
|
||||
var methods = t.GetMethods().Where(m => m.Name == action.MethodName).ToList();
|
||||
|
||||
CType t = obj.GetType();
|
||||
// get the properties and set them into a new collection of NameType wrappers
|
||||
var props = t.GetProperties().Select(p => new PropertyNameType(p, obj));
|
||||
return JsonConvert.SerializeObject(props, Formatting.Indented);
|
||||
}
|
||||
var method = methods.Count == 1 ? methods[0] : methods.FirstOrDefault(m => m.GetParameters().Length == action.Params.Length);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning,
|
||||
"Unable to find method with name {methodName} and that matches parameters {@parameters}", null, action.MethodName,
|
||||
action.Params);
|
||||
return;
|
||||
}
|
||||
var mParams = method.GetParameters();
|
||||
|
||||
var convertedParams = mParams
|
||||
.Select((p, i) => ConvertType(action.Params[i], p.ParameterType))
|
||||
.ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Calling method {methodName} on device {deviceKey} with {@params}", null, method.Name, action.DeviceKey, action.Params);
|
||||
var result = method.Invoke(obj, convertedParams);
|
||||
|
||||
// If the method returns a Task, await it
|
||||
if (result is Task task)
|
||||
{
|
||||
await task;
|
||||
}
|
||||
// If the method returns a Task<T>, await it
|
||||
else if (result != null && result.GetType().IsGenericType && result.GetType().GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
await (Task)result;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error invoking method {methodName} on device {deviceKey}", null, method.Name, action.DeviceKey);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Unable to call method with name {methodName} with {@parameters}", null, action.MethodName, action.Params);
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertType(object value, Type conversionType)
|
||||
{
|
||||
if (!conversionType.IsEnum)
|
||||
{
|
||||
return Convert.ChangeType(value, conversionType, System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var stringValue = Convert.ToString(value);
|
||||
|
||||
if (String.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
String.Format("{0} cannot be converted to a string prior to conversion to enum"));
|
||||
}
|
||||
return Enum.Parse(conversionType, stringValue, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the properties on a device
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetProperties(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
|
||||
Type t = obj.GetType();
|
||||
// get the properties and set them into a new collection of NameType wrappers
|
||||
var props = t.GetProperties().Select(p => new PropertyNameType(p, obj));
|
||||
return JsonConvert.SerializeObject(props, Formatting.Indented);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a property from a device path by name
|
||||
@@ -136,10 +208,10 @@ namespace PepperDash.Essentials.Core
|
||||
public static object GetPropertyByName(string deviceObjectPath, string propertyName)
|
||||
{
|
||||
var dev = FindObjectOnPath(deviceObjectPath);
|
||||
if(dev == null)
|
||||
if (dev == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
|
||||
object prop = dev.GetType().GetCType().GetProperty(propertyName).GetValue(dev, null);
|
||||
|
||||
object prop = dev.GetType().GetProperty(propertyName).GetValue(dev, null);
|
||||
|
||||
// var prop = t.GetProperty(propertyName);
|
||||
if (prop != null)
|
||||
@@ -153,126 +225,126 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the methods on a device
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMethods(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
/// <summary>
|
||||
/// Gets the methods on a device
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMethods(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
|
||||
// Package up method names using helper objects
|
||||
CType t = obj.GetType();
|
||||
var methods = t.GetMethods()
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.Select(p => new MethodNameParams(p));
|
||||
return JsonConvert.SerializeObject(methods, Formatting.Indented);
|
||||
}
|
||||
// Package up method names using helper objects
|
||||
Type t = obj.GetType();
|
||||
var methods = t.GetMethods()
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.Select(p => new MethodNameParams(p));
|
||||
return JsonConvert.SerializeObject(methods, Formatting.Indented);
|
||||
}
|
||||
|
||||
public static string GetApiMethods(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
public static string GetApiMethods(string deviceObjectPath)
|
||||
{
|
||||
var obj = FindObjectOnPath(deviceObjectPath);
|
||||
if (obj == null)
|
||||
return "{ \"error\":\"No Device\"}";
|
||||
|
||||
// Package up method names using helper objects
|
||||
CType t = obj.GetType();
|
||||
var methods = t.GetMethods()
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any())
|
||||
.Select(p => new MethodNameParams(p));
|
||||
return JsonConvert.SerializeObject(methods, Formatting.Indented);
|
||||
}
|
||||
|
||||
// Package up method names using helper objects
|
||||
Type t = obj.GetType();
|
||||
var methods = t.GetMethods()
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.Where(m => m.GetCustomAttributes(typeof(ApiAttribute), true).Any())
|
||||
.Select(p => new MethodNameParams(p));
|
||||
return JsonConvert.SerializeObject(methods, Formatting.Indented);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks down a dotted object path, starting with a Device, and returns the object
|
||||
/// at the end of the path
|
||||
/// </summary>
|
||||
public static object FindObjectOnPath(string deviceObjectPath)
|
||||
{
|
||||
var path = deviceObjectPath.Split('.');
|
||||
|
||||
var dev = DeviceManager.GetDeviceForKey(path[0]);
|
||||
if (dev == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]);
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Walks down a dotted object path, starting with a Device, and returns the object
|
||||
/// at the end of the path
|
||||
/// </summary>
|
||||
public static object FindObjectOnPath(string deviceObjectPath)
|
||||
{
|
||||
var path = deviceObjectPath.Split('.');
|
||||
|
||||
// loop through any dotted properties
|
||||
object obj = dev;
|
||||
if (path.Length > 1)
|
||||
{
|
||||
for (int i = 1; i < path.Length; i++)
|
||||
{
|
||||
var objName = path[i];
|
||||
string indexStr = null;
|
||||
var indexOpen = objName.IndexOf('[');
|
||||
if (indexOpen != -1)
|
||||
{
|
||||
var indexClose = objName.IndexOf(']');
|
||||
if (indexClose == -1)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets");
|
||||
return null;
|
||||
}
|
||||
// Get the index and strip quotes if any
|
||||
indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", "");
|
||||
objName = objName.Substring(0, indexOpen);
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr);
|
||||
}
|
||||
var dev = DeviceManager.GetDeviceForKey(path[0]);
|
||||
if (dev == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device {0} not found", path[0]);
|
||||
return null;
|
||||
}
|
||||
|
||||
CType oType = obj.GetType();
|
||||
var prop = oType.GetProperty(objName);
|
||||
if (prop == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]);
|
||||
return null;
|
||||
}
|
||||
// if there's an index, try to get the property
|
||||
if (indexStr != null)
|
||||
{
|
||||
if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName);
|
||||
return null;
|
||||
}
|
||||
var collection = prop.GetValue(obj, null) as ICollection;
|
||||
// Get the indexed items "property"
|
||||
var indexedPropInfo = prop.PropertyType.GetProperty("Item");
|
||||
// These are the parameters for the indexing. Only care about one
|
||||
var indexParams = indexedPropInfo.GetIndexParameters();
|
||||
if (indexParams.Length > 0)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name);
|
||||
var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType,
|
||||
System.Globalization.CultureInfo.InvariantCulture);
|
||||
try
|
||||
{
|
||||
obj = indexedPropInfo.GetValue(collection, new object[] { properParam });
|
||||
}
|
||||
// if the index is bad, catch it here.
|
||||
catch (Crestron.SimplSharp.Reflection.TargetInvocationException e)
|
||||
{
|
||||
if (e.InnerException is ArgumentOutOfRangeException)
|
||||
Debug.LogMessage(LogEventLevel.Information, " Index Out of range");
|
||||
else if (e.InnerException is KeyNotFoundException)
|
||||
Debug.LogMessage(LogEventLevel.Information, " Key not found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// loop through any dotted properties
|
||||
object obj = dev;
|
||||
if (path.Length > 1)
|
||||
{
|
||||
for (int i = 1; i < path.Length; i++)
|
||||
{
|
||||
var objName = path[i];
|
||||
string indexStr = null;
|
||||
var indexOpen = objName.IndexOf('[');
|
||||
if (indexOpen != -1)
|
||||
{
|
||||
var indexClose = objName.IndexOf(']');
|
||||
if (indexClose == -1)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "ERROR Unmatched index brackets");
|
||||
return null;
|
||||
}
|
||||
// Get the index and strip quotes if any
|
||||
indexStr = objName.Substring(indexOpen + 1, indexClose - indexOpen - 1).Replace("\"", "");
|
||||
objName = objName.Substring(0, indexOpen);
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, " Checking for collection '{0}', index '{1}'", objName, indexStr);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
obj = prop.GetValue(obj, null);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
Type oType = obj.GetType();
|
||||
var prop = oType.GetProperty(objName);
|
||||
if (prop == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} not found on {1}", objName, path[i - 1]);
|
||||
return null;
|
||||
}
|
||||
// if there's an index, try to get the property
|
||||
if (indexStr != null)
|
||||
{
|
||||
if (!typeof(ICollection).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, dev, "Property {0} is not collection", objName);
|
||||
return null;
|
||||
}
|
||||
var collection = prop.GetValue(obj, null) as ICollection;
|
||||
// Get the indexed items "property"
|
||||
var indexedPropInfo = prop.PropertyType.GetProperty("Item");
|
||||
// These are the parameters for the indexing. Only care about one
|
||||
var indexParams = indexedPropInfo.GetIndexParameters();
|
||||
if (indexParams.Length > 0)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, " Indexed, param type: {0}", indexParams[0].ParameterType.Name);
|
||||
var properParam = Convert.ChangeType(indexStr, indexParams[0].ParameterType,
|
||||
System.Globalization.CultureInfo.InvariantCulture);
|
||||
try
|
||||
{
|
||||
obj = indexedPropInfo.GetValue(collection, new object[] { properParam });
|
||||
}
|
||||
// if the index is bad, catch it here.
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
if (e.InnerException is ArgumentOutOfRangeException)
|
||||
Debug.LogMessage(LogEventLevel.Information, " Index Out of range");
|
||||
else if (e.InnerException is KeyNotFoundException)
|
||||
Debug.LogMessage(LogEventLevel.Information, " Key not found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
obj = prop.GetValue(obj, null);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property on an object.
|
||||
@@ -287,7 +359,7 @@ namespace PepperDash.Essentials.Core
|
||||
//if (obj == null)
|
||||
// return "{\"error\":\"No object found\"}";
|
||||
|
||||
//CType t = obj.GetType();
|
||||
//Type t = obj.GetType();
|
||||
|
||||
|
||||
//// get the properties and set them into a new collection of NameType wrappers
|
||||
@@ -295,78 +367,85 @@ namespace PepperDash.Essentials.Core
|
||||
//return JsonConvert.SerializeObject(props, Formatting.Indented);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class DeviceActionWrapper
|
||||
{
|
||||
public string DeviceKey { get; set; }
|
||||
public string MethodName { get; set; }
|
||||
public object[] Params { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class PropertyNameType
|
||||
{
|
||||
object Parent;
|
||||
public class DeviceActionWrapper
|
||||
{
|
||||
public string DeviceKey { get; set; }
|
||||
public string MethodName { get; set; }
|
||||
public object[] Params { get; set; }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public PropertyInfo PropInfo { get; private set; }
|
||||
public string Name { get { return PropInfo.Name; } }
|
||||
public string Type { get { return PropInfo.PropertyType.Name; } }
|
||||
public string Value { get
|
||||
public class PropertyNameType
|
||||
{
|
||||
private object Parent;
|
||||
|
||||
[JsonIgnore]
|
||||
public PropertyInfo PropInfo { get; private set; }
|
||||
public string Name { get { return PropInfo.Name; } }
|
||||
public string Type { get { return PropInfo.PropertyType.Name; } }
|
||||
public string Value
|
||||
{
|
||||
if (PropInfo.CanRead)
|
||||
get
|
||||
{
|
||||
try
|
||||
if (PropInfo.CanRead)
|
||||
{
|
||||
return PropInfo.GetValue(Parent, null).ToString();
|
||||
try
|
||||
{
|
||||
return PropInfo.GetValue(Parent, null).ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
return null;
|
||||
} }
|
||||
}
|
||||
|
||||
public bool CanRead { get { return PropInfo.CanRead; } }
|
||||
public bool CanWrite { get { return PropInfo.CanWrite; } }
|
||||
|
||||
|
||||
public PropertyNameType(PropertyInfo info, object parent)
|
||||
{
|
||||
PropInfo = info;
|
||||
public PropertyNameType(PropertyInfo info, object parent)
|
||||
{
|
||||
PropInfo = info;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MethodNameParams
|
||||
{
|
||||
[JsonIgnore]
|
||||
public MethodInfo MethodInfo { get; private set; }
|
||||
public class MethodNameParams
|
||||
{
|
||||
[JsonIgnore]
|
||||
public MethodInfo MethodInfo { get; private set; }
|
||||
|
||||
public string Name { get { return MethodInfo.Name; } }
|
||||
public IEnumerable<NameType> Params { get {
|
||||
return MethodInfo.GetParameters().Select(p =>
|
||||
new NameType { Name = p.Name, Type = p.ParameterType.Name });
|
||||
} }
|
||||
public string Name { get { return MethodInfo.Name; } }
|
||||
public IEnumerable<NameType> Params
|
||||
{
|
||||
get
|
||||
{
|
||||
return MethodInfo.GetParameters().Select(p =>
|
||||
new NameType { Name = p.Name, Type = p.ParameterType.Name });
|
||||
}
|
||||
}
|
||||
|
||||
public MethodNameParams(MethodInfo info)
|
||||
{
|
||||
MethodInfo = info;
|
||||
}
|
||||
}
|
||||
public MethodNameParams(MethodInfo info)
|
||||
{
|
||||
MethodInfo = info;
|
||||
}
|
||||
}
|
||||
|
||||
public class NameType
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
public class NameType
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class ApiAttribute : CAttribute
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class ApiAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public static class DeviceManager
|
||||
{
|
||||
public static class DeviceManager
|
||||
{
|
||||
public static event EventHandler<EventArgs> AllDevicesActivated;
|
||||
public static event EventHandler<EventArgs> AllDevicesRegistered;
|
||||
public static event EventHandler<EventArgs> AllDevicesInitialized;
|
||||
|
||||
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
|
||||
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
|
||||
//public static List<Device> Devices { get { return _Devices; } }
|
||||
//static List<Device> _Devices = new List<Device>();
|
||||
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
|
||||
private static readonly CEvent AllowAddDevicesCEvent = new CEvent(false, true);
|
||||
|
||||
static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
|
||||
//public static List<Device> Devices { get { return _Devices; } }
|
||||
//static List<Device> _Devices = new List<Device>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of all the devices in a list
|
||||
/// </summary>
|
||||
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
|
||||
private static readonly Dictionary<string, IKeyed> Devices = new Dictionary<string, IKeyed>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static bool AddDeviceEnabled;
|
||||
/// <summary>
|
||||
/// Returns a copy of all the devices in a list
|
||||
/// </summary>
|
||||
public static List<IKeyed> AllDevices { get { return new List<IKeyed>(Devices.Values); } }
|
||||
|
||||
public static void Initialize(CrestronControlSystem cs)
|
||||
{
|
||||
AddDeviceEnabled = true;
|
||||
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
public static bool AddDeviceEnabled;
|
||||
|
||||
public static void Initialize(CrestronControlSystem cs)
|
||||
{
|
||||
AddDeviceEnabled = true;
|
||||
CrestronConsole.AddNewConsoleCommand(ListDeviceCommStatuses, "devcommstatus", "Lists the communication status of all devices",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ListDeviceFeedbacks, "devfb", "Lists current feedbacks",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(ListDevices, "devlist", "Lists current managed devices",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(DeviceJsonApi.DoDeviceActionWithJson, "devjson", "",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetProperties(s)), "devprops", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetMethods(s)), "devmethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(s => CrestronConsole.ConsoleCommandResponse(DeviceJsonApi.GetApiMethods(s)), "apimethods", "", ConsoleAccessLevelEnum.AccessOperator);
|
||||
CrestronConsole.AddNewConsoleCommand(SimulateComReceiveOnDevice, "devsimreceive",
|
||||
"Simulates incoming data on a com device", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
@@ -52,77 +52,89 @@ namespace PepperDash.Essentials.Core
|
||||
CrestronConsole.AddNewConsoleCommand(s => DisableAllDeviceStreamDebugging(), "disableallstreamdebug", "disables stream debugging on all devices", ConsoleAccessLevelEnum.AccessOperator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls activate steps on all Device class items
|
||||
/// </summary>
|
||||
public static void ActivateAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls activate steps on all Device class items
|
||||
/// </summary>
|
||||
public static void ActivateAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnAllDevicesRegistered();
|
||||
|
||||
DeviceCriticalSection.Enter();
|
||||
DeviceCriticalSection.Enter();
|
||||
AddDeviceEnabled = false;
|
||||
// PreActivate all devices
|
||||
Debug.LogMessage(LogEventLevel.Information,"****PreActivation starting...****");
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).PreActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// PreActivate all devices
|
||||
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).PreActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PreActivation failure: {0}", e.Message, d.Key);
|
||||
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Information, "****PreActivation complete****");
|
||||
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
|
||||
Debug.LogMessage(LogEventLevel.Information, "****Activation starting...****");
|
||||
|
||||
// Activate all devices
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).Activate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Activate all devices
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).Activate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} Activation failure: {0}", e.Message, d.Key);
|
||||
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "****Activation complete****");
|
||||
Debug.LogMessage(LogEventLevel.Information, "****PostActivation starting...****");
|
||||
|
||||
// PostActivate all devices
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).PostActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
|
||||
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
|
||||
}
|
||||
}
|
||||
// PostActivate all devices
|
||||
foreach (var d in Devices.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (d is Device)
|
||||
(d as Device).PostActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, d, "ERROR: Device {1} PostActivation failure: {0}", e.Message, d.Key);
|
||||
Debug.LogMessage(LogEventLevel.Debug, d, "Stack Trace: {0}", e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
|
||||
|
||||
OnAllDevicesActivated();
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeviceManager_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
var allInitialized = Devices.Values.OfType<EssentialsDevice>().All(d => d.IsInitialized);
|
||||
|
||||
if (allInitialized)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "****All Devices Initalized****");
|
||||
|
||||
OnAllDevicesInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnAllDevicesActivated()
|
||||
{
|
||||
@@ -142,77 +154,85 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls activate on all Device class items
|
||||
/// </summary>
|
||||
public static void DeactivateAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
DeviceCriticalSection.Enter();
|
||||
foreach (var d in Devices.Values.OfType<Device>())
|
||||
{
|
||||
d.Deactivate();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
private static void OnAllDevicesInitialized()
|
||||
{
|
||||
var handler = AllDevicesInitialized;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(null, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
//static void ListMethods(string devKey)
|
||||
//{
|
||||
// var dev = GetDeviceForKey(devKey);
|
||||
// if(dev != null)
|
||||
// {
|
||||
// var type = dev.GetType().GetCType();
|
||||
// var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance);
|
||||
// var sb = new StringBuilder();
|
||||
// sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length));
|
||||
// foreach (var m in methods)
|
||||
// {
|
||||
// sb.Append(string.Format("{0}(", m.Name));
|
||||
// var pars = m.GetParameters();
|
||||
// foreach (var p in pars)
|
||||
// sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name));
|
||||
// sb.AppendLine(")");
|
||||
// }
|
||||
// CrestronConsole.ConsoleCommandResponse(sb.ToString());
|
||||
// }
|
||||
//}
|
||||
/// <summary>
|
||||
/// Calls activate on all Device class items
|
||||
/// </summary>
|
||||
public static void DeactivateAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
DeviceCriticalSection.Enter();
|
||||
foreach (var d in Devices.Values.OfType<Device>())
|
||||
{
|
||||
d.Deactivate();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ListDevices(string s)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
|
||||
var sorted = Devices.Values.ToList();
|
||||
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
|
||||
//static void ListMethods(string devKey)
|
||||
//{
|
||||
// var dev = GetDeviceForKey(devKey);
|
||||
// if(dev != null)
|
||||
// {
|
||||
// var type = dev.GetType().GetType();
|
||||
// var methods = type.GetMethods(BindingFlags.Public|BindingFlags.Instance);
|
||||
// var sb = new StringBuilder();
|
||||
// sb.AppendLine(string.Format("{2} methods on [{0}] ({1}):", dev.Key, type.Name, methods.Length));
|
||||
// foreach (var m in methods)
|
||||
// {
|
||||
// sb.Append(string.Format("{0}(", m.Name));
|
||||
// var pars = m.GetParameters();
|
||||
// foreach (var p in pars)
|
||||
// sb.Append(string.Format("({1}){0} ", p.Name, p.ParameterType.Name));
|
||||
// sb.AppendLine(")");
|
||||
// }
|
||||
// CrestronConsole.ConsoleCommandResponse(sb.ToString());
|
||||
// }
|
||||
//}
|
||||
|
||||
foreach (var d in sorted)
|
||||
{
|
||||
var name = d is IKeyName ? (d as IKeyName).Name : "---";
|
||||
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
|
||||
}
|
||||
}
|
||||
private static void ListDevices(string s)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "{0} Devices registered with Device Manager:", Devices.Count);
|
||||
var sorted = Devices.Values.ToList();
|
||||
sorted.Sort((a, b) => a.Key.CompareTo(b.Key));
|
||||
|
||||
private static void ListDeviceFeedbacks(string devKey)
|
||||
{
|
||||
var dev = GetDeviceForKey(devKey);
|
||||
if (dev == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
|
||||
return;
|
||||
}
|
||||
var statusDev = dev as IHasFeedback;
|
||||
if (statusDev == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
|
||||
return;
|
||||
}
|
||||
statusDev.DumpFeedbacksToConsole(true);
|
||||
}
|
||||
foreach (var d in sorted)
|
||||
{
|
||||
var name = d is IKeyName ? (d as IKeyName).Name : "---";
|
||||
Debug.LogMessage(LogEventLevel.Information, " [{0}] {1}", d.Key, name);
|
||||
}
|
||||
}
|
||||
|
||||
//static void ListDeviceCommands(string devKey)
|
||||
private static void ListDeviceFeedbacks(string devKey)
|
||||
{
|
||||
var dev = GetDeviceForKey(devKey);
|
||||
if (dev == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' not found", devKey);
|
||||
return;
|
||||
}
|
||||
if (!(dev is IHasFeedback statusDev))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device '{0}' does not have visible feedbacks", devKey);
|
||||
return;
|
||||
}
|
||||
statusDev.DumpFeedbacksToConsole(true);
|
||||
}
|
||||
|
||||
//static void ListDeviceCommands(string devKey)
|
||||
//{
|
||||
// var dev = GetDeviceForKey(devKey);
|
||||
// if (dev == null)
|
||||
@@ -223,134 +243,135 @@ namespace PepperDash.Essentials.Core
|
||||
// Debug.LogMessage(LogEventLevel.Information, "This needs to be reworked. Stay tuned.", devKey);
|
||||
//}
|
||||
|
||||
private static void ListDeviceCommStatuses(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
|
||||
{
|
||||
sb.Append(string.Format("{0}: {1}\r", dev,
|
||||
dev.CommunicationMonitor.Status));
|
||||
}
|
||||
CrestronConsole.ConsoleCommandResponse(sb.ToString());
|
||||
}
|
||||
private static void ListDeviceCommStatuses(string input)
|
||||
{
|
||||
|
||||
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}{Environment.NewLine}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//static void DoDeviceCommand(string command)
|
||||
//static void DoDeviceCommand(string command)
|
||||
//{
|
||||
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
|
||||
//}
|
||||
|
||||
public static void AddDevice(IKeyed newDev)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DeviceCriticalSection.TryEnter())
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
|
||||
return;
|
||||
}
|
||||
// Check for device with same key
|
||||
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
|
||||
////// If it exists, remove or warn??
|
||||
//if (existingDevice != null)
|
||||
public static void AddDevice(IKeyed newDev)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DeviceCriticalSection.TryEnter())
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
|
||||
return;
|
||||
}
|
||||
// Check for device with same key
|
||||
//var existingDevice = _Devices.FirstOrDefault(d => d.Key.Equals(newDev.Key, StringComparison.OrdinalIgnoreCase));
|
||||
////// If it exists, remove or warn??
|
||||
//if (existingDevice != null)
|
||||
|
||||
if (!AddDeviceEnabled)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
|
||||
return;
|
||||
}
|
||||
if (!AddDeviceEnabled)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "All devices have been activated. Adding new devices is not allowed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Devices.ContainsKey(newDev.Key))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
|
||||
return;
|
||||
}
|
||||
Devices.Add(newDev.Key, newDev);
|
||||
//if (!(_Devices.Contains(newDev)))
|
||||
// _Devices.Add(newDev);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
if (Devices.ContainsKey(newDev.Key))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, newDev, "WARNING: A device with this key already exists. Not added to manager");
|
||||
return;
|
||||
}
|
||||
Devices.Add(newDev.Key, newDev);
|
||||
//if (!(_Devices.Contains(newDev)))
|
||||
// _Devices.Add(newDev);
|
||||
|
||||
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DeviceCriticalSection.TryEnter())
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"Currently unable to add devices to Device Manager. Please try again");
|
||||
return;
|
||||
}
|
||||
if (!AddDeviceEnabled)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"All devices have been activated. Adding new devices is not allowed.");
|
||||
return;
|
||||
}
|
||||
if (newDev is EssentialsDevice essentialsDev)
|
||||
essentialsDev.Initialized += DeviceManager_Initialized;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var dev in devicesToAdd)
|
||||
{
|
||||
try
|
||||
{
|
||||
Devices.Add(dev.Key, dev);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
|
||||
dev.Key, ex.Message, ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DeviceCriticalSection.TryEnter())
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"Currently unable to add devices to Device Manager. Please try again");
|
||||
return;
|
||||
}
|
||||
if (!AddDeviceEnabled)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"All devices have been activated. Adding new devices is not allowed.");
|
||||
return;
|
||||
}
|
||||
|
||||
public static void RemoveDevice(IKeyed newDev)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var dev in devicesToAdd)
|
||||
{
|
||||
try
|
||||
{
|
||||
Devices.Add(dev.Key, dev);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Error adding device with key {0} to Device Manager: {1}\r\nStack Trace: {2}",
|
||||
dev.Key, ex.Message, ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveDevice(IKeyed newDev)
|
||||
{
|
||||
try
|
||||
{
|
||||
DeviceCriticalSection.Enter();
|
||||
if (newDev == null)
|
||||
return;
|
||||
if (Devices.ContainsKey(newDev.Key))
|
||||
Devices.Remove(newDev.Key);
|
||||
//if (_Devices.Contains(newDev))
|
||||
// _Devices.Remove(newDev);
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
if (newDev == null)
|
||||
return;
|
||||
if (Devices.ContainsKey(newDev.Key))
|
||||
Devices.Remove(newDev.Key);
|
||||
//if (_Devices.Contains(newDev))
|
||||
// _Devices.Remove(newDev);
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeviceCriticalSection.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetDeviceKeys()
|
||||
{
|
||||
//return _Devices.Select(d => d.Key).ToList();
|
||||
return Devices.Keys;
|
||||
}
|
||||
public static IEnumerable<string> GetDeviceKeys()
|
||||
{
|
||||
//return _Devices.Select(d => d.Key).ToList();
|
||||
return Devices.Keys;
|
||||
}
|
||||
|
||||
public static IEnumerable<IKeyed> GetDevices()
|
||||
{
|
||||
//return _Devices.Select(d => d.Key).ToList();
|
||||
return Devices.Values;
|
||||
}
|
||||
public static IEnumerable<IKeyed> GetDevices()
|
||||
{
|
||||
//return _Devices.Select(d => d.Key).ToList();
|
||||
return Devices.Values;
|
||||
}
|
||||
|
||||
public static IKeyed GetDeviceForKey(string key)
|
||||
{
|
||||
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
if (key != null && Devices.ContainsKey(key))
|
||||
return Devices[key];
|
||||
public static IKeyed GetDeviceForKey(string key)
|
||||
{
|
||||
//return _Devices.FirstOrDefault(d => d.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
if (key != null && Devices.ContainsKey(key))
|
||||
return Devices[key];
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Console handler that simulates com port data receive
|
||||
@@ -367,8 +388,7 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
//Debug.LogMessage(LogEventLevel.Verbose, "**** {0} - {1} ****", match.Groups[1].Value, match.Groups[2].Value);
|
||||
|
||||
var com = GetDeviceForKey(match.Groups[1].Value) as ComPortController;
|
||||
if (com == null)
|
||||
if (!(GetDeviceForKey(match.Groups[1].Value) is ComPortController com))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("'{0}' is not a comm port device", match.Groups[1].Value);
|
||||
return;
|
||||
@@ -423,16 +443,15 @@ namespace PepperDash.Essentials.Core
|
||||
var deviceKey = args[0];
|
||||
var setting = args[1];
|
||||
|
||||
var timeout= String.Empty;
|
||||
var timeout = String.Empty;
|
||||
|
||||
if (args.Length >= 3)
|
||||
{
|
||||
timeout = args[2];
|
||||
}
|
||||
|
||||
var device = GetDeviceForKey(deviceKey) as IStreamDebugging;
|
||||
|
||||
if (device == null)
|
||||
if (!(GetDeviceForKey(deviceKey) is IStreamDebugging device))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Unable to get device with key: {0}", deviceKey);
|
||||
return;
|
||||
@@ -479,13 +498,11 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
foreach (var device in AllDevices)
|
||||
{
|
||||
var streamDevice = device as IStreamDebugging;
|
||||
|
||||
if (streamDevice != null)
|
||||
if (device is IStreamDebugging streamDevice)
|
||||
{
|
||||
streamDevice.StreamDebugging.SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting.Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
@@ -17,6 +17,24 @@ namespace PepperDash.Essentials.Core
|
||||
[Description("The base Essentials Device Class")]
|
||||
public abstract class EssentialsDevice : Device
|
||||
{
|
||||
public event EventHandler Initialized;
|
||||
|
||||
private bool _isInitialized;
|
||||
public bool IsInitialized {
|
||||
get { return _isInitialized; }
|
||||
private set
|
||||
{
|
||||
if (_isInitialized == value) return;
|
||||
|
||||
_isInitialized = value;
|
||||
|
||||
if (_isInitialized)
|
||||
{
|
||||
Initialized?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected EssentialsDevice(string key)
|
||||
: base(key)
|
||||
{
|
||||
@@ -41,6 +59,8 @@ namespace PepperDash.Essentials.Core
|
||||
try
|
||||
{
|
||||
Initialize();
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public interface IHasDspPresets
|
||||
{
|
||||
List<IDspPreset> Presets { get; }
|
||||
|
||||
void RecallPreset(IDspPreset preset);
|
||||
|
||||
}
|
||||
|
||||
public interface IDspPreset
|
||||
{
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
12
src/PepperDash.Essentials.Core/Devices/IDspPresets.cs
Normal file
12
src/PepperDash.Essentials.Core/Devices/IDspPresets.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using PepperDash.Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public interface IDspPresets
|
||||
{
|
||||
Dictionary<string, IKeyName> Presets { get; }
|
||||
|
||||
void RecallPreset(string key);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
@@ -23,7 +21,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public static void DumpFeedbacksToConsole(this IHasFeedback source, bool getCurrentStates)
|
||||
{
|
||||
CType t = source.GetType();
|
||||
Type t = source.GetType();
|
||||
// get the properties and set them into a new collection of NameType wrappers
|
||||
var props = t.GetProperties().Select(p => new PropertyNameType(p, t));
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Devices;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class LevelControlListItem : AudioControlListItemBase
|
||||
{
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public IBasicVolumeWithFeedback LevelControl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_levelControl == null)
|
||||
_levelControl = DeviceManager.GetDeviceForKey(ParentDeviceKey) as IBasicVolumeWithFeedback;
|
||||
return _levelControl;
|
||||
}
|
||||
}
|
||||
IBasicVolumeWithFeedback _levelControl;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name from the device if it implements IKeyName or else returns the Name property
|
||||
/// </summary>
|
||||
[JsonProperty("preferredName")]
|
||||
public string PreferredName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Name)) return Name;
|
||||
else
|
||||
{
|
||||
if (LevelControl is IKeyName namedLevelControl)
|
||||
{
|
||||
if (namedLevelControl == null)
|
||||
return "---";
|
||||
return namedLevelControl.Name;
|
||||
}
|
||||
else return "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key of the device in the DeviceManager for control
|
||||
/// </summary>
|
||||
[JsonProperty("deviceKey")]
|
||||
public string DeviceKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if(string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
|
||||
else
|
||||
{
|
||||
return DeviceManager.AllDevices.
|
||||
Where(d => d.Key.Contains(ParentDeviceKey) && d.Key.Contains(ItemKey)).FirstOrDefault()?.Key ?? $"{ParentDeviceKey}--{ItemKey}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the item is a level, mute , or both
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public eLevelControlType Type { get; set; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum eLevelControlType
|
||||
{
|
||||
Level = 1,
|
||||
Mute = 2,
|
||||
LevelAndMute = Level | Mute,
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Crestron.SimplSharpPro;
|
||||
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using Crestron.SimplSharpPro;
|
||||
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
@@ -70,7 +69,7 @@ namespace PepperDash.Essentials.Core.Devices
|
||||
{
|
||||
public LaptopFactory()
|
||||
{
|
||||
TypeNames = new List<string>() { "laptop" };
|
||||
TypeNames = new List<string>() { "deprecated" };
|
||||
}
|
||||
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
|
||||
45
src/PepperDash.Essentials.Core/Devices/PresetListItem.cs
Normal file
45
src/PepperDash.Essentials.Core/Devices/PresetListItem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class PresetListItem : AudioControlListItemBase
|
||||
{
|
||||
[JsonIgnore]
|
||||
public IKeyName Preset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_preset == null)
|
||||
{
|
||||
var parent = DeviceManager.GetDeviceForKey(ParentDeviceKey) as IDspPresets;
|
||||
if (parent == null || !parent.Presets.ContainsKey(ItemKey))
|
||||
return null;
|
||||
_preset = parent.Presets[ItemKey];
|
||||
}
|
||||
return _preset;
|
||||
}
|
||||
}
|
||||
private IKeyName _preset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name from the device if it implements IKeyName or else returns the Name property
|
||||
/// </summary>
|
||||
[JsonProperty("preferredName")]
|
||||
public string PreferredName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Name)) return Name;
|
||||
|
||||
else return Preset.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +1,121 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharpPro;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum eSourceListItemType
|
||||
{
|
||||
Route, Off, Other, SomethingAwesomerThanThese
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum eSourceListItemType
|
||||
{
|
||||
Route, Off, Other, SomethingAwesomerThanThese
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an item in a source list - can be deserialized into.
|
||||
/// </summary>
|
||||
public class SourceListItem
|
||||
{
|
||||
[JsonProperty("sourceKey")]
|
||||
public string SourceKey { get; set; }
|
||||
/// <summary>
|
||||
/// Represents an item in a source list - can be deserialized into.
|
||||
/// </summary>
|
||||
public class SourceListItem
|
||||
{
|
||||
[JsonProperty("sourceKey")]
|
||||
public string SourceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source Device for this, if it exists in DeviceManager
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Device SourceDevice
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_SourceDevice == null)
|
||||
_SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device;
|
||||
return _SourceDevice;
|
||||
}
|
||||
}
|
||||
Device _SourceDevice;
|
||||
/// <summary>
|
||||
/// Returns the source Device for this, if it exists in DeviceManager
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Device SourceDevice
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_SourceDevice == null)
|
||||
_SourceDevice = DeviceManager.GetDeviceForKey(SourceKey) as Device;
|
||||
return _SourceDevice;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets either the source's Name or this AlternateName property, if
|
||||
/// defined. If source doesn't exist, returns "Missing source"
|
||||
/// </summary>
|
||||
[JsonProperty("preferredName")]
|
||||
public string PreferredName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
if (SourceDevice == null)
|
||||
return "---";
|
||||
return SourceDevice.Name;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
private Device _SourceDevice;
|
||||
|
||||
/// <summary>
|
||||
/// A name that will override the source's name on the UI
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets either the source's Name or this AlternateName property, if
|
||||
/// defined. If source doesn't exist, returns "Missing source"
|
||||
/// </summary>
|
||||
[JsonProperty("preferredName")]
|
||||
public string PreferredName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
{
|
||||
if (SourceDevice == null)
|
||||
return "---";
|
||||
return SourceDevice.Name;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A name that will override the source's name on the UI
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies and icon for the source list item
|
||||
/// </summary>
|
||||
[JsonProperty("icon")]
|
||||
public string Icon { get; set; }
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alternate icon
|
||||
/// </summary>
|
||||
[JsonProperty("altIcon")]
|
||||
public string AltIcon { get; set; }
|
||||
public string AltIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the item should be included in the source list
|
||||
/// </summary>
|
||||
[JsonProperty("includeInSourceList")]
|
||||
public bool IncludeInSourceList { get; set; }
|
||||
public bool IncludeInSourceList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to specify the order of the items in the source list when displayed
|
||||
/// </summary>
|
||||
[JsonProperty("order")]
|
||||
public int Order { get; set; }
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The key of the device for volume control
|
||||
/// </summary>
|
||||
[JsonProperty("volumeControlKey")]
|
||||
public string VolumeControlKey { get; set; }
|
||||
public string VolumeControlKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of source list item
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eSourceListItemType Type { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public eSourceListItemType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of routes to execute for this source list item
|
||||
/// </summary>
|
||||
[JsonProperty("routeList")]
|
||||
public List<SourceRouteListItem> RouteList { get; set; }
|
||||
public List<SourceRouteListItem> RouteList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if this source should be disabled for sharing to the far end call participants via codec content
|
||||
/// </summary>
|
||||
[JsonProperty("disableCodecSharing")]
|
||||
public bool DisableCodecSharing { get; set; }
|
||||
public bool DisableCodecSharing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if this source should be disabled for routing to a shared output
|
||||
/// </summary>
|
||||
[JsonProperty("disableRoutedSharing")]
|
||||
public bool DisableRoutedSharing { get; set; }
|
||||
public bool DisableRoutedSharing { get; set; }
|
||||
|
||||
[JsonProperty("destinations")]
|
||||
public List<eSourceListItemDestinationTypes> Destinations { get; set; }
|
||||
@@ -144,25 +137,46 @@ namespace PepperDash.Essentials.Core
|
||||
[JsonProperty("isAudioSource")]
|
||||
public bool IsAudioSource { get; set; }
|
||||
|
||||
public SourceListItem()
|
||||
{
|
||||
Icon = "Blank";
|
||||
}
|
||||
/// <summary>
|
||||
/// Hide source on UI when Avanced Sharing is enabled
|
||||
/// </summary>
|
||||
[JsonProperty("disableAdvancedRouting")]
|
||||
public bool DisableAdvancedRouting { get; set; }
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Hide source on UI when Simpl Sharing is enabled
|
||||
/// </summary>
|
||||
[JsonProperty("disableSimpleRouting")]
|
||||
public bool DisableSimpleRouting { get; set; }
|
||||
|
||||
public class SourceRouteListItem
|
||||
{
|
||||
[JsonProperty("sourceKey")]
|
||||
public string SourceKey { get; set; }
|
||||
public SourceListItem()
|
||||
{
|
||||
Icon = "Blank";
|
||||
}
|
||||
|
||||
[JsonProperty("destinationKey")]
|
||||
public string DestinationKey { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{SourceKey}:{Name}";
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("type")]
|
||||
public eRoutingSignalType Type { get; set; }
|
||||
}
|
||||
public class SourceRouteListItem
|
||||
{
|
||||
[JsonProperty("sourceKey")]
|
||||
public string SourceKey { get; set; }
|
||||
|
||||
[JsonProperty("sourcePortKey")]
|
||||
public string SourcePortKey { get; set; }
|
||||
|
||||
[JsonProperty("destinationKey")]
|
||||
public string DestinationKey { get; set; }
|
||||
|
||||
[JsonProperty("destinationPortKey")]
|
||||
public string DestinationPortKey { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public eRoutingSignalType Type { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the valid destination types for SourceListItems in a room
|
||||
@@ -172,7 +186,12 @@ namespace PepperDash.Essentials.Core
|
||||
defaultDisplay,
|
||||
leftDisplay,
|
||||
rightDisplay,
|
||||
centerDisplay,
|
||||
programAudio,
|
||||
codecContent
|
||||
codecContent,
|
||||
frontLeftDisplay,
|
||||
frontRightDisplay,
|
||||
rearLeftDisplay,
|
||||
rearRightDisplay,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Bridges;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
using Crestron.SimplSharpPro.DM.Endpoints;
|
||||
using Crestron.SimplSharpPro.DM.Endpoints.Transmitters;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Bridges;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||
public abstract class DisplayBase : EssentialsDevice, IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking
|
||||
{
|
||||
public event SourceInfoChangeHandler CurrentSourceChange;
|
||||
public event InputChangedEventHandler InputChanged;
|
||||
|
||||
public string CurrentSourceInfoKey { get; set; }
|
||||
public SourceListItem CurrentSourceInfo
|
||||
@@ -96,7 +94,9 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void ExecuteSwitch(object selector);
|
||||
public RoutingInputPort CurrentInputPort => throw new NotImplementedException();
|
||||
|
||||
public abstract void ExecuteSwitch(object selector);
|
||||
|
||||
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
|
||||
EiscApiAdvanced bridge)
|
||||
@@ -266,16 +266,16 @@ namespace PepperDash.Essentials.Core
|
||||
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
|
||||
|
||||
|
||||
public static MockDisplay DefaultDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DefaultDisplay == null)
|
||||
_DefaultDisplay = new MockDisplay("default", "Default Display");
|
||||
return _DefaultDisplay;
|
||||
}
|
||||
}
|
||||
static MockDisplay _DefaultDisplay;
|
||||
// public static MockDisplay DefaultDisplay
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (_DefaultDisplay == null)
|
||||
// _DefaultDisplay = new MockDisplay("default", "Default Display");
|
||||
// return _DefaultDisplay;
|
||||
// }
|
||||
//}
|
||||
//static MockDisplay _DefaultDisplay;
|
||||
|
||||
public TwoWayDisplayBase(string key, string name)
|
||||
: base(key, name)
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
using Crestron.SimplSharpPro.DM.Endpoints;
|
||||
using Crestron.SimplSharpPro.DM.Endpoints.Transmitters;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Bridges;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||
public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced
|
||||
|
||||
{
|
||||
public RoutingInputPort HdmiIn1 { get; private set; }
|
||||
public RoutingInputPort HdmiIn2 { get; private set; }
|
||||
public RoutingInputPort HdmiIn3 { get; private set; }
|
||||
public RoutingInputPort ComponentIn1 { get; private set; }
|
||||
public RoutingInputPort VgaIn1 { get; private set; }
|
||||
|
||||
bool _PowerIsOn;
|
||||
bool _IsWarmingUp;
|
||||
bool _IsCoolingDown;
|
||||
|
||||
protected override Func<bool> PowerIsOnFeedbackFunc
|
||||
{
|
||||
get
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "*************************************************** Display Power is {0}", _PowerIsOn ? "on" : "off");
|
||||
return _PowerIsOn;
|
||||
};
|
||||
} }
|
||||
protected override Func<bool> IsCoolingDownFeedbackFunc
|
||||
{
|
||||
get
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "*************************************************** {0}", _IsCoolingDown ? "Display is cooling down" : "Display has finished cooling down");
|
||||
return _IsCoolingDown;
|
||||
};
|
||||
}
|
||||
}
|
||||
protected override Func<bool> IsWarmingUpFeedbackFunc
|
||||
{
|
||||
get
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "*************************************************** {0}", _IsWarmingUp ? "Display is warming up" : "Display has finished warming up");
|
||||
return _IsWarmingUp;
|
||||
};
|
||||
}
|
||||
}
|
||||
protected override Func<string> CurrentInputFeedbackFunc { get { return () => "Not Implemented"; } }
|
||||
|
||||
int VolumeHeldRepeatInterval = 200;
|
||||
ushort VolumeInterval = 655;
|
||||
ushort _FakeVolumeLevel = 31768;
|
||||
bool _IsMuted;
|
||||
|
||||
public MockDisplay(string key, string name)
|
||||
: base(key, name)
|
||||
{
|
||||
HdmiIn1 = new RoutingInputPort(RoutingPortNames.HdmiIn1, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
||||
eRoutingPortConnectionType.Hdmi, null, this);
|
||||
HdmiIn2 = new RoutingInputPort(RoutingPortNames.HdmiIn2, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
||||
eRoutingPortConnectionType.Hdmi, null, this);
|
||||
HdmiIn3 = new RoutingInputPort(RoutingPortNames.HdmiIn3, eRoutingSignalType.Audio | eRoutingSignalType.Video,
|
||||
eRoutingPortConnectionType.Hdmi, null, this);
|
||||
ComponentIn1 = new RoutingInputPort(RoutingPortNames.ComponentIn, eRoutingSignalType.Video,
|
||||
eRoutingPortConnectionType.Component, null, this);
|
||||
VgaIn1 = new RoutingInputPort(RoutingPortNames.VgaIn, eRoutingSignalType.Video,
|
||||
eRoutingPortConnectionType.Composite, null, this);
|
||||
InputPorts.AddRange(new[] { HdmiIn1, HdmiIn2, HdmiIn3, ComponentIn1, VgaIn1 });
|
||||
|
||||
VolumeLevelFeedback = new IntFeedback(() => { return _FakeVolumeLevel; });
|
||||
MuteFeedback = new BoolFeedback("MuteOn", () => _IsMuted);
|
||||
|
||||
WarmupTime = 10000;
|
||||
CooldownTime = 10000;
|
||||
}
|
||||
|
||||
public override void PowerOn()
|
||||
{
|
||||
if (!PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown)
|
||||
{
|
||||
_IsWarmingUp = true;
|
||||
IsWarmingUpFeedback.InvokeFireUpdate();
|
||||
// Fake power-up cycle
|
||||
WarmupTimer = new CTimer(o =>
|
||||
{
|
||||
_IsWarmingUp = false;
|
||||
_PowerIsOn = true;
|
||||
IsWarmingUpFeedback.InvokeFireUpdate();
|
||||
PowerIsOnFeedback.InvokeFireUpdate();
|
||||
}, WarmupTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PowerOff()
|
||||
{
|
||||
// If a display has unreliable-power off feedback, just override this and
|
||||
// remove this check.
|
||||
if (PowerIsOnFeedback.BoolValue && !_IsWarmingUp && !_IsCoolingDown)
|
||||
{
|
||||
_IsCoolingDown = true;
|
||||
IsCoolingDownFeedback.InvokeFireUpdate();
|
||||
// Fake cool-down cycle
|
||||
CooldownTimer = new CTimer(o =>
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Cooldown timer ending");
|
||||
_IsCoolingDown = false;
|
||||
IsCoolingDownFeedback.InvokeFireUpdate();
|
||||
_PowerIsOn = false;
|
||||
PowerIsOnFeedback.InvokeFireUpdate();
|
||||
}, CooldownTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PowerToggle()
|
||||
{
|
||||
if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue)
|
||||
PowerOff();
|
||||
else if (!PowerIsOnFeedback.BoolValue && !IsCoolingDownFeedback.BoolValue)
|
||||
PowerOn();
|
||||
}
|
||||
|
||||
public override void ExecuteSwitch(object selector)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "ExecuteSwitch: {0}", selector);
|
||||
|
||||
if (!_PowerIsOn)
|
||||
{
|
||||
PowerOn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region IBasicVolumeWithFeedback Members
|
||||
|
||||
public IntFeedback VolumeLevelFeedback { get; private set; }
|
||||
|
||||
public void SetVolume(ushort level)
|
||||
{
|
||||
_FakeVolumeLevel = level;
|
||||
VolumeLevelFeedback.InvokeFireUpdate();
|
||||
}
|
||||
|
||||
public void MuteOn()
|
||||
{
|
||||
_IsMuted = true;
|
||||
MuteFeedback.InvokeFireUpdate();
|
||||
}
|
||||
|
||||
public void MuteOff()
|
||||
{
|
||||
_IsMuted = false;
|
||||
MuteFeedback.InvokeFireUpdate();
|
||||
}
|
||||
|
||||
public BoolFeedback MuteFeedback { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IBasicVolumeControls Members
|
||||
|
||||
public void VolumeUp(bool pressRelease)
|
||||
{
|
||||
//while (pressRelease)
|
||||
//{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Down {0}", pressRelease);
|
||||
if (pressRelease)
|
||||
{
|
||||
var newLevel = _FakeVolumeLevel + VolumeInterval;
|
||||
SetVolume((ushort)newLevel);
|
||||
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval);
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
public void VolumeDown(bool pressRelease)
|
||||
{
|
||||
//while (pressRelease)
|
||||
//{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Up {0}", pressRelease);
|
||||
if (pressRelease)
|
||||
{
|
||||
var newLevel = _FakeVolumeLevel - VolumeInterval;
|
||||
SetVolume((ushort)newLevel);
|
||||
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval);
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
public void MuteToggle()
|
||||
{
|
||||
_IsMuted = !_IsMuted;
|
||||
MuteFeedback.InvokeFireUpdate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
|
||||
{
|
||||
LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Please use PepperDash.Essentials.Devices.Common, this will be removed in 2.1")]
|
||||
public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay>
|
||||
{
|
||||
public MockDisplayFactory()
|
||||
{
|
||||
TypeNames = new List<string>() { "mockdisplay" };
|
||||
}
|
||||
|
||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");
|
||||
return new MockDisplay(dc.Key, dc.Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
@@ -14,13 +14,13 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class DeviceFactoryWrapper
|
||||
{
|
||||
public CType CType { get; set; }
|
||||
public Type Type { get; set; }
|
||||
public string Description { get; set; }
|
||||
public Func<DeviceConfig, IKeyed> FactoryMethod { get; set; }
|
||||
|
||||
public DeviceFactoryWrapper()
|
||||
{
|
||||
CType = null;
|
||||
Type = null;
|
||||
Description = "Not Available";
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
var factory = (IDeviceFactory)Crestron.SimplSharp.Reflection.Activator.CreateInstance(type);
|
||||
var factory = (IDeviceFactory)Activator.CreateInstance(type);
|
||||
factory.LoadTypeFactories();
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -69,7 +69,7 @@ namespace PepperDash.Essentials.Core
|
||||
DeviceFactory.FactoryMethods.Add(typeName, new DeviceFactoryWrapper() { FactoryMethod = method});
|
||||
}
|
||||
|
||||
public static void AddFactoryForType(string typeName, string description, CType cType, Func<DeviceConfig, IKeyed> method)
|
||||
public static void AddFactoryForType(string typeName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace PepperDash.Essentials.Core
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapper = new DeviceFactoryWrapper() { CType = cType, Description = description, FactoryMethod = method };
|
||||
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
|
||||
DeviceFactory.FactoryMethods.Add(typeName, wrapper);
|
||||
}
|
||||
|
||||
@@ -149,18 +149,7 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "Exception occurred while creating device {0}: {1}", dc.Key, ex.Message);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.StackTrace);
|
||||
|
||||
if (ex.InnerException == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Error, "Inner exception while creating device {0}: {1}", dc.Key,
|
||||
ex.InnerException.Message);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "{0}", ex.InnerException.StackTrace);
|
||||
Debug.LogMessage(ex, "Exception occurred while creating device {0}: {1}", null, dc.Key, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -180,17 +169,17 @@ namespace PepperDash.Essentials.Core
|
||||
foreach (var type in types.OrderBy(t => t.Key))
|
||||
{
|
||||
var description = type.Value.Description;
|
||||
var cType = "Not Specified by Plugin";
|
||||
var Type = "Not Specified by Plugin";
|
||||
|
||||
if (type.Value.CType != null)
|
||||
if (type.Value.Type != null)
|
||||
{
|
||||
cType = type.Value.CType.FullName;
|
||||
Type = type.Value.Type.FullName;
|
||||
}
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
@"Type: '{0}'
|
||||
CType: '{1}'
|
||||
Description: {2}{3}", type.Key, cType, description, CrestronEnvironment.NewLine);
|
||||
Type: '{1}'
|
||||
Description: {2}{3}", type.Key, Type, description, CrestronEnvironment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a class that is capable of loading device types
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
@@ -25,7 +25,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
var factory = (IProcessorExtensionDeviceFactory)Crestron.SimplSharp.Reflection.Activator.CreateInstance(extension);
|
||||
var factory = (IProcessorExtensionDeviceFactory)Activator.CreateInstance(extension);
|
||||
factory.LoadFactories();
|
||||
}
|
||||
catch( Exception e )
|
||||
@@ -55,7 +55,7 @@ namespace PepperDash.Essentials.Core
|
||||
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, new DeviceFactoryWrapper() { FactoryMethod = method });
|
||||
}
|
||||
|
||||
public static void AddFactoryForType(string extensionName, string description, CType cType, Func<DeviceConfig, IKeyed> method)
|
||||
public static void AddFactoryForType(string extensionName, string description, Type Type, Func<DeviceConfig, IKeyed> method)
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Debug, "Adding factory method for type '{0}'", typeName);
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace PepperDash.Essentials.Core
|
||||
return;
|
||||
}
|
||||
|
||||
var wrapper = new DeviceFactoryWrapper() { CType = cType, Description = description, FactoryMethod = method };
|
||||
var wrapper = new DeviceFactoryWrapper() { Type = Type, Description = description, FactoryMethod = method };
|
||||
ProcessorExtensionDeviceFactory.ProcessorExtensionFactoryMethods.Add(extensionName, wrapper);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronXml;
|
||||
@@ -14,6 +10,10 @@ using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Fusion
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Globalization;
|
||||
@@ -35,7 +33,8 @@ namespace PepperDash.Essentials.Core
|
||||
public static eCrestronSeries ProcessorSeries { get { return CrestronEnvironment.ProgramCompatibility; } }
|
||||
|
||||
// TODO: consider making this configurable later
|
||||
public static IFormatProvider Culture = CultureInfo.CreateSpecificCulture("en-US");
|
||||
public static IFormatProvider Culture = CultureInfo.InvariantCulture;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True when the processor type is a DMPS variant
|
||||
@@ -137,6 +136,7 @@ namespace PepperDash.Essentials.Core
|
||||
public static void SetFilePathPrefix(string prefix)
|
||||
{
|
||||
FilePathPrefix = prefix;
|
||||
Debug.LogMessage(LogEventLevel.Information, "File Path Prefix set to '{0}'", FilePathPrefix);
|
||||
}
|
||||
|
||||
static string _AssemblyVersion;
|
||||
@@ -278,6 +278,18 @@ namespace PepperDash.Essentials.Core
|
||||
CrestronConsole.PrintLine("Error starting CrestronDataStoreStatic: {0}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("en");
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture("en");
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
// If specific culture fails, fall back to invariant
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using Crestron.SimplSharp.Scheduler;
|
||||
|
||||
using PepperDash.Core;
|
||||
@@ -22,15 +21,31 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
static Scheduler()
|
||||
{
|
||||
CrestronConsole.AddNewConsoleCommand(DeleteEventGroup, "DeleteEventGroup", "Deletes the event group by key", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(ClearEventsFromGroup, "ClearAllEvents", "Clears all scheduled events for this group", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(ListAllEventGroups, "ListAllEventGroups", "Lists all the event groups by key", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
CrestronConsole.AddNewConsoleCommand(ListAllEventsForGroup, "ListEventsForGroup",
|
||||
"Lists all events for the given group", ConsoleAccessLevelEnum.AccessOperator);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void DeleteEventGroup(string groupName)
|
||||
{
|
||||
if (EventGroups.ContainsKey(groupName))
|
||||
{
|
||||
var group = EventGroups[groupName];
|
||||
|
||||
EventGroups.Remove(groupName);
|
||||
|
||||
group.Dispose();
|
||||
|
||||
group = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears (deletes) all events from a group
|
||||
/// </summary>
|
||||
@@ -40,7 +55,7 @@ namespace PepperDash.Essentials.Core
|
||||
if (!EventGroups.ContainsKey(groupName))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.",
|
||||
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null,
|
||||
groupName);
|
||||
return;
|
||||
}
|
||||
@@ -51,47 +66,47 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
group.ClearAllEvents();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", groupName);
|
||||
Debug.LogMessage(LogEventLevel.Information, "[Scheduler]: All events deleted from group '{0}'", null, groupName);
|
||||
}
|
||||
else
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.",
|
||||
"[Scheduler]: Unable to delete events from group '{0}'. Group not found in EventGroups dictionary.", null,
|
||||
groupName);
|
||||
}
|
||||
|
||||
static void ListAllEventGroups(string command)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Event Groups:");
|
||||
CrestronConsole.ConsoleCommandResponse("Event Groups:");
|
||||
foreach (var group in EventGroups)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "{0}", group.Key);
|
||||
CrestronConsole.ConsoleCommandResponse($"{group.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
static void ListAllEventsForGroup(string args)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", args);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Getting events for group {0}...", null, args);
|
||||
|
||||
ScheduledEventGroup group;
|
||||
|
||||
if (!EventGroups.TryGetValue(args, out group))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", args);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unabled to get event group for key {0}", null, args);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var evt in group.ScheduledEvents)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information,
|
||||
@"
|
||||
****Event key {0}****
|
||||
Event date/time: {1}
|
||||
Persistent: {2}
|
||||
Acknowlegable: {3}
|
||||
Recurrence: {4}
|
||||
Recurrence Days: {5}
|
||||
********************", evt.Key, evt.Value.DateAndTime, evt.Value.Persistent, evt.Value.Acknowledgeable,
|
||||
evt.Value.Recurrence.Recurrence, evt.Value.Recurrence.RecurrenceDays);
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
$@"
|
||||
****Event key {evt.Key}****
|
||||
Event state: {evt.Value.EventState}
|
||||
Event date/time: {evt.Value.DateAndTime}
|
||||
Persistent: {evt.Value.Persistent}
|
||||
Acknowlegable: {evt.Value.Acknowledgeable}
|
||||
Recurrence: {evt.Value.Recurrence.Recurrence}
|
||||
Recurrence Days: {evt.Value.Recurrence.RecurrenceDays}
|
||||
********************");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,10 +153,9 @@ Recurrence Days: {5}
|
||||
|
||||
var dayOfWeek = eventTime.DayOfWeek;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}", dayOfWeek);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler]: eventTime day of week is: {0}",null, dayOfWeek);
|
||||
switch (dayOfWeek)
|
||||
{
|
||||
{
|
||||
case DayOfWeek.Sunday:
|
||||
{
|
||||
if ((recurrence & ScheduledEventCommon.eWeekDays.Sunday) == ScheduledEventCommon.eWeekDays.Sunday)
|
||||
@@ -203,53 +217,64 @@ Recurrence Days: {5}
|
||||
|
||||
public static void CreateEventFromConfig(ScheduledEventConfig config, ScheduledEventGroup group, ScheduledEvent.UserEventCallBack handler)
|
||||
{
|
||||
if (group == null)
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null");
|
||||
return;
|
||||
if (group == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to create event. Group is null", null, null);
|
||||
return;
|
||||
}
|
||||
var scheduledEvent = new ScheduledEvent(config.Key, group)
|
||||
{
|
||||
Acknowledgeable = config.Acknowledgeable,
|
||||
Persistent = config.Persistent
|
||||
};
|
||||
|
||||
scheduledEvent.UserCallBack += handler;
|
||||
|
||||
scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday);
|
||||
|
||||
var eventTime = DateTime.Parse(config.Time);
|
||||
|
||||
if (DateTime.Now > eventTime)
|
||||
{
|
||||
eventTime = eventTime.AddDays(1);
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", null, eventTime.DayOfWeek,
|
||||
config.Days);
|
||||
|
||||
var dayOfWeekConverted = ConvertDayOfWeek(eventTime);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", null, dayOfWeekConverted);
|
||||
|
||||
while (!dayOfWeekConverted.IsFlagSet(config.Days))
|
||||
{
|
||||
eventTime = eventTime.AddDays(1);
|
||||
|
||||
dayOfWeekConverted = ConvertDayOfWeek(eventTime);
|
||||
}
|
||||
|
||||
scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime);
|
||||
|
||||
scheduledEvent.Recurrence.Weekly(config.Days);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, $"[Scheduler] Event State: {scheduledEvent.EventState}", null, null);
|
||||
|
||||
if (config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Enabled)
|
||||
{
|
||||
scheduledEvent.Enable();
|
||||
}
|
||||
else if (!config.Enable && scheduledEvent.EventState != ScheduledEventCommon.eEventState.Disabled)
|
||||
{
|
||||
scheduledEvent.Disable();
|
||||
}
|
||||
|
||||
}
|
||||
var scheduledEvent = new ScheduledEvent(config.Key, group)
|
||||
catch (Exception e)
|
||||
{
|
||||
Acknowledgeable = config.Acknowledgeable,
|
||||
Persistent = config.Persistent
|
||||
};
|
||||
|
||||
scheduledEvent.UserCallBack += handler;
|
||||
|
||||
scheduledEvent.DateAndTime.SetFirstDayOfWeek(ScheduledEventCommon.eFirstDayOfWeek.Sunday);
|
||||
|
||||
var eventTime = DateTime.Parse(config.Time);
|
||||
|
||||
if (DateTime.Now > eventTime)
|
||||
{
|
||||
eventTime = eventTime.AddDays(1);
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "[Scheduler] Current Date day of week: {0} recurrence days: {1}", eventTime.DayOfWeek,
|
||||
config.Days);
|
||||
|
||||
var dayOfWeekConverted = ConvertDayOfWeek(eventTime);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "[Scheduler] eventTime Day: {0}", dayOfWeekConverted);
|
||||
|
||||
while (!dayOfWeekConverted.IsFlagSet(config.Days))
|
||||
{
|
||||
eventTime = eventTime.AddDays(1);
|
||||
|
||||
dayOfWeekConverted = ConvertDayOfWeek(eventTime);
|
||||
}
|
||||
|
||||
scheduledEvent.DateAndTime.SetAbsoluteEventTime(eventTime);
|
||||
|
||||
scheduledEvent.Recurrence.Weekly(config.Days);
|
||||
|
||||
if (config.Enable)
|
||||
{
|
||||
scheduledEvent.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduledEvent.Disable();
|
||||
Debug.LogMessage(LogEventLevel.Error, "Error creating scheduled event: {0}", null, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
@@ -107,19 +107,20 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
|
||||
protected void AddJoins(Type type)
|
||||
{
|
||||
{
|
||||
var fields =
|
||||
type.GetCType()
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(f => f.IsDefined(typeof (JoinNameAttribute), true));
|
||||
type.GetFields(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(f => f.IsDefined(typeof (JoinNameAttribute), true)).ToList();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Got {fields} with JoinNameAttribute", fields.Count);
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var childClass = Convert.ChangeType(this, type, null);
|
||||
|
||||
var value = field.GetValue(childClass) as JoinDataComplete; //this here is JoinMapBaseAdvanced, not the child class. JoinMapBaseAdvanced has no fields.
|
||||
//this here is JoinMapBaseAdvanced, not the child class. JoinMapBaseAdvanced has no fields.
|
||||
|
||||
if (value == null)
|
||||
if (!(field.GetValue(childClass) is JoinDataComplete value))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to cast base class to {0}", type.Name);
|
||||
continue;
|
||||
@@ -129,7 +130,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
var joinName = value.GetNameAttribute(field);
|
||||
|
||||
if (String.IsNullOrEmpty(joinName)) continue;
|
||||
if (string.IsNullOrEmpty(joinName)) continue;
|
||||
|
||||
Joins.Add(joinName, value);
|
||||
}
|
||||
@@ -155,29 +156,37 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Get the joins of each type and print them
|
||||
sb.AppendLine(String.Format("# {0}", GetType().Name));
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## Digitals");
|
||||
sb.AppendLine();
|
||||
// Get the joins of each type and print them
|
||||
var digitals =
|
||||
Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Digital) == eJoinType.Digital)
|
||||
.ToDictionary(j => j.Key, j => j.Value);
|
||||
var digitalSb = AppendJoinList(GetSortedJoins(digitals));
|
||||
digitalSb.AppendLine("## Analogs");
|
||||
digitalSb.AppendLine();
|
||||
var lineEnding = "\r\n";
|
||||
|
||||
var analogs =
|
||||
Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Analog) == eJoinType.Analog)
|
||||
var digitals =
|
||||
Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Digital))
|
||||
.ToDictionary(j => j.Key, j => j.Value);
|
||||
|
||||
var analogs = Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Analog))
|
||||
.ToDictionary(j => j.Key, j => j.Value);
|
||||
var analogSb = AppendJoinList(GetSortedJoins(analogs));
|
||||
analogSb.AppendLine("## Serials");
|
||||
analogSb.AppendLine();
|
||||
|
||||
var serials =
|
||||
Joins.Where(j => (j.Value.Metadata.JoinType & eJoinType.Serial) == eJoinType.Serial)
|
||||
Joins.Where(j => j.Value.Metadata.JoinType.HasFlag(eJoinType.Serial))
|
||||
.ToDictionary(j => j.Key, j => j.Value);
|
||||
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Debug, "Digital join count {digitalCount} Analog join count {analogCount} Serial join count {serialCount}", null, digitals.Count, analogs.Count, serials.Count);
|
||||
|
||||
// Get the joins of each type and print them
|
||||
sb.Append($"# {GetType().Name}\r\n");
|
||||
sb.Append(lineEnding);
|
||||
sb.Append($"## Digitals{lineEnding}");
|
||||
sb.Append(lineEnding);
|
||||
// Get the joins of each type and print them
|
||||
|
||||
var digitalSb = AppendJoinList(GetSortedJoins(digitals));
|
||||
digitalSb.Append($"## Analogs{lineEnding}");
|
||||
digitalSb.Append(lineEnding);
|
||||
|
||||
var analogSb = AppendJoinList(GetSortedJoins(analogs));
|
||||
analogSb.Append($"## Serials{lineEnding}");
|
||||
analogSb.Append(lineEnding);
|
||||
|
||||
|
||||
var serialSb = AppendJoinList(GetSortedJoins(serials));
|
||||
|
||||
sb.EnsureCapacity(sb.Length + digitalSb.Length + analogSb.Length + serialSb.Length);
|
||||
@@ -202,7 +211,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
private static void WriteJoinmapMarkdown(StringBuilder stringBuilder, string pluginType, string bridgeKey, string deviceKey)
|
||||
{
|
||||
var fileName = String.Format("{0}{1}{2}__{3}__{4}.md", Global.FilePathPrefix, "joinMaps/", pluginType, bridgeKey, deviceKey);
|
||||
var fileName = string.Format("{0}{1}{2}__{3}__{4}.md", Global.FilePathPrefix, "joinMaps/", pluginType, bridgeKey, deviceKey);
|
||||
|
||||
using (var sw = new StreamWriter(fileName))
|
||||
{
|
||||
@@ -230,7 +239,7 @@ namespace PepperDash.Essentials.Core
|
||||
static StringBuilder AppendJoinList(List<KeyValuePair<string, JoinDataComplete>> joins)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
const string stringFormatter = "| {0} | {1} | {2} | {3} | {4} |";
|
||||
const string stringFormatter = "| {0} | {1} | {2} | {3} | {4} |\r\n";
|
||||
const int joinNumberLen = 11;
|
||||
const int joinSpanLen = 9;
|
||||
const int typeLen = 19;
|
||||
@@ -238,25 +247,25 @@ namespace PepperDash.Essentials.Core
|
||||
var descriptionLen = (from @join in joins select @join.Value into j select j.Metadata.Description.Length).Concat(new[] {11}).Max();
|
||||
|
||||
//build header
|
||||
sb.AppendLine(String.Format(stringFormatter,
|
||||
String.Format("Join Number").PadRight(joinNumberLen, ' '),
|
||||
String.Format("Join Span").PadRight(joinSpanLen, ' '),
|
||||
String.Format("Description").PadRight(descriptionLen, ' '),
|
||||
String.Format("Type").PadRight(typeLen, ' '),
|
||||
String.Format("Capabilities").PadRight(capabilitiesLen, ' ')));
|
||||
sb.Append(string.Format(stringFormatter,
|
||||
string.Format("Join Number").PadRight(joinNumberLen, ' '),
|
||||
string.Format("Join Span").PadRight(joinSpanLen, ' '),
|
||||
string.Format("Description").PadRight(descriptionLen, ' '),
|
||||
string.Format("Type").PadRight(typeLen, ' '),
|
||||
string.Format("Capabilities").PadRight(capabilitiesLen, ' ')));
|
||||
//build table seperator
|
||||
sb.AppendLine(String.Format(stringFormatter,
|
||||
new String('-', joinNumberLen),
|
||||
new String('-', joinSpanLen),
|
||||
new String('-', descriptionLen),
|
||||
new String('-', typeLen),
|
||||
new String('-', capabilitiesLen)));
|
||||
sb.Append(string.Format(stringFormatter,
|
||||
new string('-', joinNumberLen),
|
||||
new string('-', joinSpanLen),
|
||||
new string('-', descriptionLen),
|
||||
new string('-', typeLen),
|
||||
new string('-', capabilitiesLen)));
|
||||
|
||||
foreach (var join in joins)
|
||||
{
|
||||
sb.AppendLine(join.Value.GetMarkdownFormattedData(stringFormatter, descriptionLen));
|
||||
sb.Append(join.Value.GetMarkdownFormattedData(stringFormatter, descriptionLen));
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.Append("\r\n");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -411,10 +420,10 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
|
||||
//Fixed Width Headers
|
||||
var joinNumberLen = String.Format("Join Number").Length;
|
||||
var joinSpanLen = String.Format("Join Span").Length;
|
||||
var typeLen = String.Format("AnalogDigitalSerial").Length;
|
||||
var capabilitiesLen = String.Format("ToFromFusion").Length;
|
||||
var joinNumberLen = string.Format("Join Number").Length;
|
||||
var joinSpanLen = string.Format("Join Span").Length;
|
||||
var typeLen = string.Format("AnalogDigitalSerial").Length;
|
||||
var capabilitiesLen = string.Format("ToFromFusion").Length;
|
||||
|
||||
//Track which one failed, if it did
|
||||
const string placeholder = "unknown";
|
||||
@@ -430,13 +439,13 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
try
|
||||
{
|
||||
dataArray["joinNumber"] = String.Format("{0}", JoinNumber.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinNumberLen, ' ');
|
||||
dataArray["joinSpan"] = String.Format("{0}", JoinSpan.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinSpanLen, ' ');
|
||||
dataArray["description"] = String.Format("{0}", Metadata.Description.ReplaceIfNullOrEmpty(placeholder)).PadRight(descriptionLen, ' ');
|
||||
dataArray["joinType"] = String.Format("{0}", Metadata.JoinType.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(typeLen, ' ');
|
||||
dataArray["capabilities"] = String.Format("{0}", Metadata.JoinCapabilities.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(capabilitiesLen, ' ');
|
||||
dataArray["joinNumber"] = string.Format("{0}", JoinNumber.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinNumberLen, ' ');
|
||||
dataArray["joinSpan"] = string.Format("{0}", JoinSpan.ToString(CultureInfo.InvariantCulture).ReplaceIfNullOrEmpty(placeholder)).PadRight(joinSpanLen, ' ');
|
||||
dataArray["description"] = string.Format("{0}", Metadata.Description.ReplaceIfNullOrEmpty(placeholder)).PadRight(descriptionLen, ' ');
|
||||
dataArray["joinType"] = string.Format("{0}", Metadata.JoinType.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(typeLen, ' ');
|
||||
dataArray["capabilities"] = string.Format("{0}", Metadata.JoinCapabilities.ToString().ReplaceIfNullOrEmpty(placeholder)).PadRight(capabilitiesLen, ' ');
|
||||
|
||||
return String.Format(stringFormatter,
|
||||
return string.Format(stringFormatter,
|
||||
dataArray["joinNumber"],
|
||||
dataArray["joinSpan"],
|
||||
dataArray["description"],
|
||||
@@ -454,8 +463,8 @@ namespace PepperDash.Essentials.Core
|
||||
errorKey = item.Key;
|
||||
break;
|
||||
}
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to decode join metadata {1}- {0}", e.Message, !String.IsNullOrEmpty(errorKey) ? (' ' + errorKey) : String.Empty);
|
||||
return String.Format(stringFormatter,
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to decode join metadata {1}- {0}", e.Message, !string.IsNullOrEmpty(errorKey) ? (' ' + errorKey) : string.Empty);
|
||||
return string.Format(stringFormatter,
|
||||
dataArray["joinNumber"],
|
||||
dataArray["joinSpan"],
|
||||
dataArray["description"],
|
||||
@@ -501,7 +510,7 @@ namespace PepperDash.Essentials.Core
|
||||
public string GetNameAttribute(MemberInfo memberInfo)
|
||||
{
|
||||
var name = string.Empty;
|
||||
var attribute = (JoinNameAttribute)CAttribute.GetCustomAttribute(memberInfo, typeof(JoinNameAttribute));
|
||||
var attribute = (JoinNameAttribute)Attribute.GetCustomAttribute(memberInfo, typeof(JoinNameAttribute));
|
||||
|
||||
if (attribute == null) return name;
|
||||
|
||||
@@ -514,13 +523,13 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public class JoinNameAttribute : CAttribute
|
||||
public class JoinNameAttribute : Attribute
|
||||
{
|
||||
private string _Name;
|
||||
|
||||
public JoinNameAttribute(string name)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Setting Attribute Name: {0}", name);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Setting Attribute Name: {0}",null, name);
|
||||
_Name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace PepperDash.Essentials.Core.Monitoring
|
||||
_uptimePollTimer = null;
|
||||
}
|
||||
|
||||
private void PollUptime(object obj)
|
||||
public void PollUptime(object obj)
|
||||
{
|
||||
var consoleResponse = string.Empty;
|
||||
|
||||
@@ -142,19 +142,22 @@ namespace PepperDash.Essentials.Core.Monitoring
|
||||
_uptime = uptimeRaw.Substring(forIndex + 4);
|
||||
}
|
||||
|
||||
private static void ProcessorReboot()
|
||||
public static void ProcessorReboot()
|
||||
{
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return;
|
||||
Debug.LogMessage(LogEventLevel.Information, "Rebooting...");
|
||||
|
||||
var response = string.Empty;
|
||||
|
||||
var response = string.Empty;
|
||||
CrestronConsole.SendControlSystemCommand("reboot", ref response);
|
||||
}
|
||||
|
||||
private static void ProgramReset(uint index)
|
||||
public static void ProgramReset(uint index)
|
||||
{
|
||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) return;
|
||||
Debug.LogMessage(LogEventLevel.Information, "Resetting Program {0}...", index);
|
||||
|
||||
if (index <= 0 || index > 10) return;
|
||||
if (index <= 0 || index > 10) return;
|
||||
|
||||
var cmd = string.Format("progreset -p:{0}", index);
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using PepperDash.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
@@ -17,9 +14,36 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
private IPartitionStateProvider _partitionSensor;
|
||||
|
||||
private bool isInAutoMode;
|
||||
public bool IsInAutoMode { get; private set; }
|
||||
|
||||
private bool partitionPresent;
|
||||
private bool _partitionPresent;
|
||||
|
||||
public bool PartitionPresent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
return _partitionSensor.PartitionPresentFeedback.BoolValue;
|
||||
}
|
||||
|
||||
return _partitionPresent;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_partitionPresent == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_partitionPresent = value;
|
||||
|
||||
if (PartitionPresentFeedback != null)
|
||||
{
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EssentialsPartitionController(string key, string name, IPartitionStateProvider sensor, bool defaultToManualMode, List<string> adjacentRoomKeys)
|
||||
{
|
||||
@@ -50,11 +74,11 @@ namespace PepperDash.Essentials.Core
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
{
|
||||
if (isInAutoMode)
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
PartitionPresent = e.BoolValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +88,9 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
public void SetAutoMode()
|
||||
{
|
||||
isInAutoMode = true;
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Auto Mode", this);
|
||||
|
||||
IsInAutoMode = true;
|
||||
if (PartitionPresentFeedback != null)
|
||||
{
|
||||
PartitionPresentFeedback.SetValueFunc(() => _partitionSensor.PartitionPresentFeedback.BoolValue);
|
||||
@@ -76,52 +102,64 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
if (_partitionSensor != null)
|
||||
{
|
||||
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
|
||||
_partitionSensor.PartitionPresentFeedback.OutputChange += PartitionPresentFeedback_OutputChange;
|
||||
PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue;
|
||||
}
|
||||
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
public void SetManualMode()
|
||||
{
|
||||
isInAutoMode = false;
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Setting {Key} to Manual Mode", this);
|
||||
|
||||
IsInAutoMode = false;
|
||||
if (PartitionPresentFeedback != null)
|
||||
{
|
||||
PartitionPresentFeedback.SetValueFunc(() => partitionPresent);
|
||||
PartitionPresentFeedback.SetValueFunc(() => _partitionPresent);
|
||||
}
|
||||
else
|
||||
{
|
||||
PartitionPresentFeedback = new BoolFeedback(() => partitionPresent);
|
||||
PartitionPresentFeedback = new BoolFeedback(() => _partitionPresent);
|
||||
}
|
||||
|
||||
if (_partitionSensor != null)
|
||||
{
|
||||
_partitionSensor.PartitionPresentFeedback.OutputChange -= PartitionPresentFeedback_OutputChange;
|
||||
PartitionPresent = _partitionSensor.PartitionPresentFeedback.BoolValue;
|
||||
}
|
||||
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
|
||||
|
||||
public void SetPartitionStatePresent()
|
||||
{
|
||||
if (!isInAutoMode)
|
||||
if (!IsInAutoMode)
|
||||
{
|
||||
partitionPresent = true;
|
||||
PartitionPresent = true;
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPartitionStateNotPresent()
|
||||
{
|
||||
if (!isInAutoMode)
|
||||
if (!IsInAutoMode)
|
||||
{
|
||||
partitionPresent = false;
|
||||
PartitionPresent = false;
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggglePartitionState()
|
||||
{
|
||||
if (!isInAutoMode)
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"Toggling Partition State for {Key}", this);
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, $"IsInAutoMode: {IsInAutoMode}", this);
|
||||
|
||||
if (!IsInAutoMode)
|
||||
{
|
||||
partitionPresent = !partitionPresent;
|
||||
PartitionPresent = !PartitionPresent;
|
||||
PartitionPresentFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
@@ -13,7 +9,11 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public interface IPartitionStateProvider : IKeyName
|
||||
{
|
||||
[JsonIgnore]
|
||||
BoolFeedback PartitionPresentFeedback { get; }
|
||||
|
||||
[JsonProperty("partitionPresent")]
|
||||
bool PartitionPresent { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -21,8 +21,12 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public interface IPartitionController : IPartitionStateProvider
|
||||
{
|
||||
[JsonProperty("adjacentRoomKeys")]
|
||||
List<string> AdjacentRoomKeys { get; }
|
||||
|
||||
[JsonProperty("isInAutoMode")]
|
||||
bool IsInAutoMode { get; }
|
||||
|
||||
void SetPartitionStatePresent();
|
||||
|
||||
void SetPartitionStateNotPresent();
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
<Configurations>Debug;Release;Debug 4.7.2</Configurations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;net6</TargetFrameworks>
|
||||
<TargetFramework>net8</TargetFramework>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<AssemblyName>PepperDash_Essentials_Core</AssemblyName>
|
||||
<RootNamespace>PepperDash.Essentials.Core</RootNamespace>
|
||||
<Title>PepperDash Essentials Core</Title>
|
||||
<PackageId>PepperDash.Essentials.Core</PackageId>
|
||||
<InformationalVersion>$(Version)</InformationalVersion>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>2.0.0-local</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
@@ -22,8 +25,8 @@
|
||||
<DebugType>pdbonly</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.42" />
|
||||
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-402" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
|
||||
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-462" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.Reflection;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core;
|
||||
using Serilog.Events;
|
||||
using Newtonsoft.Json;
|
||||
using CrestronIO = Crestron.SimplSharp.CrestronIO;
|
||||
using SystemIO = System.IO;
|
||||
|
||||
namespace PepperDash.Essentials
|
||||
{
|
||||
@@ -27,24 +31,52 @@ namespace PepperDash.Essentials
|
||||
/// </summary>
|
||||
static List<LoadedAssembly> LoadedPluginFolderAssemblies;
|
||||
|
||||
/// <summary>
|
||||
/// List of plugins that were found to be incompatible with .NET 8
|
||||
/// </summary>
|
||||
public static List<IncompatiblePlugin> IncompatiblePlugins { get; private set; }
|
||||
|
||||
public static LoadedAssembly EssentialsAssembly { get; private set; }
|
||||
|
||||
public static LoadedAssembly PepperDashCoreAssembly { get; private set; }
|
||||
|
||||
public static List<LoadedAssembly> EssentialsPluginAssemblies { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory to look in for .cplz plugin packages
|
||||
/// </summary>
|
||||
static string _pluginDirectory = Global.FilePathPrefix + "plugins";
|
||||
static string _pluginDirectory => Global.FilePathPrefix + "plugins";
|
||||
|
||||
/// <summary>
|
||||
/// The directory where plugins will be moved to and loaded from
|
||||
/// </summary>
|
||||
static string _loadedPluginsDirectoryPath = _pluginDirectory + Global.DirectorySeparator + "loadedAssemblies";
|
||||
static string _loadedPluginsDirectoryPath => _pluginDirectory + Global.DirectorySeparator + "loadedAssemblies";
|
||||
|
||||
// The temp directory where .cplz archives will be unzipped to
|
||||
static string _tempDirectory = _pluginDirectory + Global.DirectorySeparator + "temp";
|
||||
static string _tempDirectory => _pluginDirectory + Global.DirectorySeparator + "temp";
|
||||
|
||||
// Known incompatible types in .NET 8
|
||||
private static readonly HashSet<string> KnownIncompatibleTypes = new HashSet<string>
|
||||
{
|
||||
"System.Net.ICertificatePolicy",
|
||||
"System.Security.Cryptography.SHA1CryptoServiceProvider",
|
||||
"System.Web.HttpUtility",
|
||||
"System.Configuration.ConfigurationManager",
|
||||
"System.Web.Services.Protocols.SoapHttpClientProtocol",
|
||||
"System.Runtime.Remoting",
|
||||
"System.EnterpriseServices",
|
||||
"System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
|
||||
"System.Security.SecurityManager",
|
||||
"System.Security.Permissions.FileIOPermission",
|
||||
"System.AppDomain.CreateDomain"
|
||||
};
|
||||
|
||||
static PluginLoader()
|
||||
{
|
||||
LoadedAssemblies = new List<LoadedAssembly>();
|
||||
LoadedPluginFolderAssemblies = new List<LoadedAssembly>();
|
||||
EssentialsPluginAssemblies = new List<LoadedAssembly>();
|
||||
IncompatiblePlugins = new List<IncompatiblePlugin>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,12 +86,12 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials");
|
||||
// Get the loaded assembly filenames
|
||||
var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
|
||||
var appDi = new SystemIO.DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
|
||||
var assemblyFiles = appDi.GetFiles("*.dll");
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length);
|
||||
|
||||
foreach (var fi in assemblyFiles)
|
||||
foreach (var fi in assemblyFiles.Where(fi => fi.Name.Contains("Essentials") || fi.Name.Contains("PepperDash")))
|
||||
{
|
||||
string version = string.Empty;
|
||||
Assembly assembly = null;
|
||||
@@ -69,6 +101,7 @@ namespace PepperDash.Essentials
|
||||
case ("PepperDashEssentials.dll"):
|
||||
{
|
||||
version = Global.AssemblyVersion;
|
||||
EssentialsAssembly = new LoadedAssembly(fi.Name, version, assembly);
|
||||
break;
|
||||
}
|
||||
case ("PepperDash_Essentials_Core.dll"):
|
||||
@@ -81,9 +114,12 @@ namespace PepperDash.Essentials
|
||||
version = Global.AssemblyVersion;
|
||||
break;
|
||||
}
|
||||
case ("PepperDash_Core.dll"):
|
||||
case ("PepperDashCore.dll"):
|
||||
{
|
||||
version = PepperDash.Core.Debug.PepperDashCoreVersion;
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Found PepperDash_Core.dll");
|
||||
version = Debug.PepperDashCoreVersion;
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "PepperDash_Core Version: {0}", version);
|
||||
PepperDashCoreAssembly = new LoadedAssembly(fi.Name, version, assembly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +138,6 @@ namespace PepperDash.Essentials
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void SetEssentialsAssembly(string name, Assembly assembly)
|
||||
{
|
||||
var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name));
|
||||
@@ -113,30 +148,159 @@ namespace PepperDash.Essentials
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a plugin is compatible with .NET 8 by examining its metadata
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the plugin assembly</param>
|
||||
/// <returns>Tuple with compatibility result, reason if incompatible, and referenced assemblies</returns>
|
||||
public static (bool IsCompatible, string Reason, List<string> References) IsPluginCompatibleWithNet8(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> referencedAssemblies = new List<string>();
|
||||
|
||||
using (SystemIO.FileStream fs = new SystemIO.FileStream(filePath, SystemIO.FileMode.Open,
|
||||
SystemIO.FileAccess.Read, SystemIO.FileShare.ReadWrite))
|
||||
using (PEReader peReader = new PEReader(fs))
|
||||
{
|
||||
if (!peReader.HasMetadata)
|
||||
return (false, "Not a valid .NET assembly", referencedAssemblies);
|
||||
|
||||
MetadataReader metadataReader = peReader.GetMetadataReader();
|
||||
|
||||
// Collect assembly references
|
||||
foreach (var assemblyRefHandle in metadataReader.AssemblyReferences)
|
||||
{
|
||||
var assemblyRef = metadataReader.GetAssemblyReference(assemblyRefHandle);
|
||||
string assemblyName = metadataReader.GetString(assemblyRef.Name);
|
||||
referencedAssemblies.Add(assemblyName);
|
||||
}
|
||||
|
||||
// Check for references to known incompatible types
|
||||
foreach (var typeRefHandle in metadataReader.TypeReferences)
|
||||
{
|
||||
var typeRef = metadataReader.GetTypeReference(typeRefHandle);
|
||||
string typeNamespace = metadataReader.GetString(typeRef.Namespace);
|
||||
string typeName = metadataReader.GetString(typeRef.Name);
|
||||
|
||||
string fullTypeName = $"{typeNamespace}.{typeName}";
|
||||
if (KnownIncompatibleTypes.Contains(fullTypeName))
|
||||
{
|
||||
return (false, $"Uses incompatible type: {fullTypeName}", referencedAssemblies);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for explicit .NET 8 compatibility attribute
|
||||
bool hasNet8Attribute = false;
|
||||
foreach (var customAttributeHandle in metadataReader.GetAssemblyDefinition().GetCustomAttributes())
|
||||
{
|
||||
var customAttribute = metadataReader.GetCustomAttribute(customAttributeHandle);
|
||||
var ctorHandle = customAttribute.Constructor;
|
||||
|
||||
if (ctorHandle.Kind == HandleKind.MemberReference)
|
||||
{
|
||||
var memberRef = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle);
|
||||
var typeRef = metadataReader.GetTypeReference((TypeReferenceHandle)memberRef.Parent);
|
||||
|
||||
string typeName = metadataReader.GetString(typeRef.Name);
|
||||
if (typeName == "Net8CompatibleAttribute" || typeName == "TargetFrameworkAttribute")
|
||||
{
|
||||
hasNet8Attribute = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNet8Attribute)
|
||||
{
|
||||
return (true, null, referencedAssemblies);
|
||||
}
|
||||
|
||||
// If we can't determine incompatibility, assume it's compatible
|
||||
return (true, null, referencedAssemblies);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, $"Error analyzing assembly: {ex.Message}", new List<string>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
static LoadedAssembly LoadAssembly(string filePath)
|
||||
/// <param name="filePath">Path to the assembly file</param>
|
||||
/// <param name="requestedBy">Name of the plugin requesting this assembly (null for direct loads)</param>
|
||||
static LoadedAssembly LoadAssembly(string filePath, string requestedBy = null)
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load {0}", filePath);
|
||||
var assembly = Assembly.LoadFrom(filePath);
|
||||
if (assembly != null)
|
||||
try
|
||||
{
|
||||
var assyVersion = GetAssemblyVersion(assembly);
|
||||
// Check .NET 8 compatibility before loading
|
||||
var (isCompatible, reason, references) = IsPluginCompatibleWithNet8(filePath);
|
||||
if (!isCompatible)
|
||||
{
|
||||
string fileName = CrestronIO.Path.GetFileName(filePath);
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason);
|
||||
|
||||
var incompatiblePlugin = new IncompatiblePlugin(fileName, reason, requestedBy);
|
||||
IncompatiblePlugins.Add(incompatiblePlugin);
|
||||
return null;
|
||||
}
|
||||
|
||||
var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly);
|
||||
LoadedAssemblies.Add(loadedAssembly);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version);
|
||||
return loadedAssembly;
|
||||
var assembly = Assembly.LoadFrom(filePath);
|
||||
if (assembly != null)
|
||||
{
|
||||
var assyVersion = GetAssemblyVersion(assembly);
|
||||
var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly);
|
||||
LoadedAssemblies.Add(loadedAssembly);
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version);
|
||||
return loadedAssembly;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else
|
||||
catch(SystemIO.FileLoadException ex) when (ex.Message.Contains("Assembly with same name is already loaded"))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath);
|
||||
// Get the assembly name from the file path
|
||||
string assemblyName = CrestronIO.Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
// Try to find the already loaded assembly
|
||||
var existingAssembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existingAssembly != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Assembly '{0}' is already loaded, using existing instance", assemblyName);
|
||||
var assyVersion = GetAssemblyVersion(existingAssembly);
|
||||
var loadedAssembly = new LoadedAssembly(existingAssembly.GetName().Name, assyVersion, existingAssembly);
|
||||
LoadedAssemblies.Add(loadedAssembly);
|
||||
return loadedAssembly;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Assembly with same name already loaded but couldn't find it: {0}", filePath);
|
||||
return null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
string fileName = CrestronIO.Path.GetFileName(filePath);
|
||||
|
||||
// Check if this might be a .NET Framework compatibility issue
|
||||
if (ex.Message.Contains("Could not load type") ||
|
||||
ex.Message.Contains("Unable to load one or more of the requested types"))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "Error loading assembly {0}: Likely .NET 8 compatibility issue: {1}",
|
||||
fileName, ex.Message);
|
||||
IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +308,7 @@ namespace PepperDash.Essentials
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <returns></returns>
|
||||
static string GetAssemblyVersion(Assembly assembly)
|
||||
public static string GetAssemblyVersion(Assembly assembly)
|
||||
{
|
||||
var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
|
||||
if (ver != null && ver.Length > 0)
|
||||
@@ -190,13 +354,33 @@ namespace PepperDash.Essentials
|
||||
/// <param name="command"></param>
|
||||
public static void ReportAssemblyVersions(string command)
|
||||
{
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("Loaded Assemblies:" + CrestronEnvironment.NewLine);
|
||||
foreach (var assembly in LoadedAssemblies)
|
||||
CrestronConsole.ConsoleCommandResponse("Essentials Version: {0}" + CrestronEnvironment.NewLine, Global.AssemblyVersion);
|
||||
CrestronConsole.ConsoleCommandResponse("PepperDash Core Version: {0}" + CrestronEnvironment.NewLine, PepperDashCoreAssembly.Version);
|
||||
CrestronConsole.ConsoleCommandResponse("Essentials Plugin Versions:" + CrestronEnvironment.NewLine);
|
||||
foreach (var assembly in EssentialsPluginAssemblies)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version);
|
||||
}
|
||||
|
||||
if (IncompatiblePlugins.Count > 0)
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("Incompatible Plugins:" + CrestronEnvironment.NewLine);
|
||||
foreach (var plugin in IncompatiblePlugins)
|
||||
{
|
||||
if (plugin.TriggeredBy != "Direct load")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine,
|
||||
plugin.Name, plugin.Reason, plugin.TriggeredBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine,
|
||||
plugin.Name, plugin.Reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves any .dll assemblies not already loaded from the plugins folder to loadedPlugins folder
|
||||
/// </summary>
|
||||
@@ -204,14 +388,14 @@ namespace PepperDash.Essentials
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Looking for .dll assemblies from plugins folder...");
|
||||
|
||||
var pluginDi = new DirectoryInfo(_pluginDirectory);
|
||||
var pluginDi = new SystemIO.DirectoryInfo(_pluginDirectory);
|
||||
var pluginFiles = pluginDi.GetFiles("*.dll");
|
||||
|
||||
if (pluginFiles.Length > 0)
|
||||
{
|
||||
if (!Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
if (!SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(_loadedPluginsDirectoryPath);
|
||||
SystemIO.Directory.CreateDirectory(_loadedPluginsDirectoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,14 +412,14 @@ namespace PepperDash.Essentials
|
||||
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + pluginFile.Name;
|
||||
|
||||
// Check if there is a previous file in the loadedPlugins directory and delete
|
||||
if (File.Exists(filePath))
|
||||
if (SystemIO.File.Exists(filePath))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
|
||||
File.Delete(filePath);
|
||||
SystemIO.File.Delete(filePath);
|
||||
}
|
||||
|
||||
// Move the file
|
||||
File.Move(pluginFile.FullName, filePath);
|
||||
SystemIO.File.Move(pluginFile.FullName, filePath);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath);
|
||||
}
|
||||
else
|
||||
@@ -259,21 +443,21 @@ namespace PepperDash.Essentials
|
||||
static void UnzipAndMoveCplzArchives()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from plugins folder...");
|
||||
var di = new DirectoryInfo(_pluginDirectory);
|
||||
var di = new SystemIO.DirectoryInfo(_pluginDirectory);
|
||||
var zFiles = di.GetFiles("*.cplz");
|
||||
|
||||
if (zFiles.Length > 0)
|
||||
{
|
||||
if (!Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
if (!SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(_loadedPluginsDirectoryPath);
|
||||
SystemIO.Directory.CreateDirectory(_loadedPluginsDirectoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var zfi in zFiles)
|
||||
{
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
var tempDi = new DirectoryInfo(_tempDirectory);
|
||||
SystemIO.Directory.CreateDirectory(_tempDirectory);
|
||||
var tempDi = new SystemIO.DirectoryInfo(_tempDirectory);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.Name);
|
||||
var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName);
|
||||
@@ -291,14 +475,14 @@ namespace PepperDash.Essentials
|
||||
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + tempFile.Name;
|
||||
|
||||
// Check if there is a previous file in the loadedPlugins directory and delete
|
||||
if (File.Exists(filePath))
|
||||
if (SystemIO.File.Exists(filePath))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
|
||||
File.Delete(filePath);
|
||||
SystemIO.File.Delete(filePath);
|
||||
}
|
||||
|
||||
// Move the file
|
||||
File.Move(tempFile.FullName, filePath);
|
||||
SystemIO.File.Move(tempFile.FullName, filePath);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath);
|
||||
}
|
||||
else
|
||||
@@ -314,7 +498,7 @@ namespace PepperDash.Essentials
|
||||
}
|
||||
|
||||
// Delete the .cplz and the temp directory
|
||||
Directory.Delete(_tempDirectory, true);
|
||||
SystemIO.Directory.Delete(_tempDirectory, true);
|
||||
zfi.Delete();
|
||||
}
|
||||
|
||||
@@ -327,16 +511,40 @@ namespace PepperDash.Essentials
|
||||
static void LoadPluginAssemblies()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder...");
|
||||
var pluginDi = new DirectoryInfo(_loadedPluginsDirectoryPath);
|
||||
var pluginDi = new CrestronIO.DirectoryInfo(_loadedPluginsDirectoryPath);
|
||||
var pluginFiles = pluginDi.GetFiles("*.dll");
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length);
|
||||
|
||||
// First, check compatibility of all assemblies before loading any
|
||||
var assemblyCompatibility = new Dictionary<string, (bool IsCompatible, string Reason, List<string> References)>();
|
||||
|
||||
foreach (var pluginFile in pluginFiles)
|
||||
{
|
||||
var loadedAssembly = LoadAssembly(pluginFile.FullName);
|
||||
|
||||
LoadedPluginFolderAssemblies.Add(loadedAssembly);
|
||||
string fileName = pluginFile.Name;
|
||||
assemblyCompatibility[fileName] = IsPluginCompatibleWithNet8(pluginFile.FullName);
|
||||
}
|
||||
|
||||
// Now load compatible assemblies and track incompatible ones
|
||||
foreach (var pluginFile in pluginFiles)
|
||||
{
|
||||
string fileName = pluginFile.Name;
|
||||
var (isCompatible, reason, references) = assemblyCompatibility[fileName];
|
||||
|
||||
if (!isCompatible)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason);
|
||||
IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, reason, null));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the assembly
|
||||
var loadedAssembly = LoadAssembly(pluginFile.FullName, null);
|
||||
|
||||
if (loadedAssembly != null)
|
||||
{
|
||||
LoadedPluginFolderAssemblies.Add(loadedAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded.");
|
||||
@@ -348,23 +556,66 @@ namespace PepperDash.Essentials
|
||||
static void LoadCustomPluginTypes()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loading Custom Plugin Types...");
|
||||
|
||||
foreach (var loadedAssembly in LoadedPluginFolderAssemblies)
|
||||
{
|
||||
// Skip if assembly is null (can happen if we had loading issues)
|
||||
if (loadedAssembly == null || loadedAssembly.Assembly == null)
|
||||
continue;
|
||||
|
||||
// iteratate this assembly's classes, looking for "LoadPlugin()" methods
|
||||
try
|
||||
{
|
||||
var assy = loadedAssembly.Assembly;
|
||||
CType[] types = {};
|
||||
Type[] types = {};
|
||||
try
|
||||
{
|
||||
types = assy.GetTypes();
|
||||
Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}");
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}",
|
||||
loadedAssembly.Name, e.Message);
|
||||
|
||||
// Check if any of the loader exceptions are due to missing assemblies
|
||||
foreach (var loaderEx in e.LoaderExceptions)
|
||||
{
|
||||
if (loaderEx is SystemIO.FileNotFoundException fileNotFoundEx)
|
||||
{
|
||||
string missingAssembly = fileNotFoundEx.FileName;
|
||||
if (!string.IsNullOrEmpty(missingAssembly))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Assembly {0} requires missing dependency: {1}",
|
||||
loadedAssembly.Name, missingAssembly);
|
||||
|
||||
// Add to incompatible plugins with dependency information
|
||||
IncompatiblePlugins.Add(new IncompatiblePlugin(
|
||||
CrestronIO.Path.GetFileName(missingAssembly),
|
||||
$"Missing dependency required by {loadedAssembly.Name}",
|
||||
loadedAssembly.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace);
|
||||
continue;
|
||||
}
|
||||
catch (TypeLoadException e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}",
|
||||
loadedAssembly.Name, e.Message);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace);
|
||||
|
||||
// Add to incompatible plugins if this is likely a .NET 8 compatibility issue
|
||||
if (e.Message.Contains("Could not load type") ||
|
||||
e.Message.Contains("Unable to load one or more of the requested types"))
|
||||
{
|
||||
IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name,
|
||||
$"Type loading error: {e.Message}",
|
||||
null));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -375,7 +626,7 @@ namespace PepperDash.Essentials
|
||||
if (typeof (IPluginDeviceFactory).IsAssignableFrom(type) && !type.IsAbstract)
|
||||
{
|
||||
var plugin =
|
||||
(IPluginDeviceFactory) Crestron.SimplSharp.Reflection.Activator.CreateInstance(type);
|
||||
(IPluginDeviceFactory)Activator.CreateInstance(type);
|
||||
LoadCustomPlugin(plugin, loadedAssembly);
|
||||
}
|
||||
}
|
||||
@@ -389,22 +640,66 @@ namespace PepperDash.Essentials
|
||||
loadedAssembly.Name, e.Message, type.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Error Loading assembly {0}: {1}",
|
||||
loadedAssembly.Name, e.Message);
|
||||
loadedAssembly.Name, e.Message);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "{0}", e.StackTrace);
|
||||
|
||||
// Add to incompatible plugins if this is likely a .NET 8 compatibility issue
|
||||
if (e.Message.Contains("Could not load type") ||
|
||||
e.Message.Contains("Unable to load one or more of the requested types"))
|
||||
{
|
||||
IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name,
|
||||
$"Assembly loading error: {e.Message}",
|
||||
null));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Update incompatible plugins with dependency information
|
||||
var pluginDependencies = new Dictionary<string, List<string>>();
|
||||
// Populate pluginDependencies with relevant data
|
||||
// Example: pluginDependencies["PluginA"] = new List<string> { "Dependency1", "Dependency2" };
|
||||
UpdateIncompatiblePluginDependencies(pluginDependencies);
|
||||
|
||||
// plugin dll will be loaded. Any classes in plugin should have a static constructor
|
||||
// that registers that class with the Core.DeviceFactory
|
||||
Debug.LogMessage(LogEventLevel.Information, "Done Loading Custom Plugin Types.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates incompatible plugins with information about which plugins depend on them
|
||||
/// </summary>
|
||||
private static void UpdateIncompatiblePluginDependencies(Dictionary<string, List<string>> pluginDependencies)
|
||||
{
|
||||
// For each incompatible plugin
|
||||
foreach (var incompatiblePlugin in IncompatiblePlugins)
|
||||
{
|
||||
// If it already has a requestedBy, skip it
|
||||
if (incompatiblePlugin.TriggeredBy != "Direct load")
|
||||
continue;
|
||||
|
||||
// Find plugins that depend on this incompatible plugin
|
||||
foreach (var plugin in pluginDependencies)
|
||||
{
|
||||
string pluginName = plugin.Key;
|
||||
List<string> dependencies = plugin.Value;
|
||||
|
||||
// If this plugin depends on the incompatible plugin
|
||||
if (dependencies.Contains(incompatiblePlugin.Name) ||
|
||||
dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ",")))
|
||||
{
|
||||
incompatiblePlugin.UpdateTriggeringPlugin(pluginName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Loads a
|
||||
/// </summary>
|
||||
@@ -432,6 +727,9 @@ namespace PepperDash.Essentials
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loading plugin: {0}", loadedAssembly.Name);
|
||||
plugin.LoadTypeFactories();
|
||||
|
||||
if(!EssentialsPluginAssemblies.Contains(loadedAssembly))
|
||||
EssentialsPluginAssemblies.Add(loadedAssembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -439,7 +737,7 @@ namespace PepperDash.Essentials
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="loadPlugin"></param>
|
||||
static void LoadCustomLegacyPlugin(CType type, MethodInfo loadPlugin, LoadedAssembly loadedAssembly)
|
||||
static void LoadCustomLegacyPlugin(Type type, MethodInfo loadPlugin, LoadedAssembly loadedAssembly)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "LoadPlugin method found in {0}", type.Name);
|
||||
|
||||
@@ -478,7 +776,6 @@ namespace PepperDash.Essentials
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Loading legacy plugin: {0}", loadedAssembly.Name);
|
||||
loadPlugin.Invoke(null, null);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -486,7 +783,9 @@ namespace PepperDash.Essentials
|
||||
/// </summary>
|
||||
public static void LoadPlugins()
|
||||
{
|
||||
if (Directory.Exists(_pluginDirectory))
|
||||
Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", _pluginDirectory);
|
||||
|
||||
if (SystemIO.Directory.Exists(_pluginDirectory))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Plugins directory found, checking for plugins");
|
||||
|
||||
@@ -496,7 +795,7 @@ namespace PepperDash.Essentials
|
||||
// Deal with any .cplz files
|
||||
UnzipAndMoveCplzArchives();
|
||||
|
||||
if (Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
if (SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
|
||||
{
|
||||
// Load the assemblies from the loadedPlugins folder into the AppDomain
|
||||
LoadPluginAssemblies();
|
||||
@@ -504,9 +803,27 @@ namespace PepperDash.Essentials
|
||||
// Load the types from any custom plugin assemblies
|
||||
LoadCustomPluginTypes();
|
||||
}
|
||||
|
||||
// Report on incompatible plugins
|
||||
if (IncompatiblePlugins.Count > 0)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, "Found {0} incompatible plugins:", IncompatiblePlugins.Count);
|
||||
foreach (var plugin in IncompatiblePlugins)
|
||||
{
|
||||
if (plugin.TriggeredBy != "Direct load")
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})",
|
||||
plugin.Name, plugin.Reason, plugin.TriggeredBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}",
|
||||
plugin.Name, plugin.Reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -514,8 +831,11 @@ namespace PepperDash.Essentials
|
||||
/// </summary>
|
||||
public class LoadedAssembly
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; private set; }
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; private set; }
|
||||
[JsonIgnore]
|
||||
public Assembly Assembly { get; private set; }
|
||||
|
||||
public LoadedAssembly(string name, string version, Assembly assembly)
|
||||
@@ -530,4 +850,52 @@ namespace PepperDash.Essentials
|
||||
Assembly = assembly;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a plugin that was found to be incompatible with .NET 8
|
||||
/// </summary>
|
||||
public class IncompatiblePlugin
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
[JsonProperty("reason")]
|
||||
public string Reason { get; private set; }
|
||||
|
||||
[JsonProperty("triggeredBy")]
|
||||
public string TriggeredBy { get; private set; }
|
||||
|
||||
public IncompatiblePlugin(string name, string reason, string triggeredBy = null)
|
||||
{
|
||||
Name = name;
|
||||
Reason = reason;
|
||||
TriggeredBy = triggeredBy ?? "Direct load";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the plugin that triggered this incompatibility
|
||||
/// </summary>
|
||||
/// <param name="triggeringPlugin">Name of the plugin that requires this incompatible plugin</param>
|
||||
public void UpdateTriggeringPlugin(string triggeringPlugin)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(triggeringPlugin))
|
||||
{
|
||||
TriggeredBy = triggeringPlugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to explicitly mark a plugin as .NET 8 compatible
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public class Net8CompatibleAttribute : Attribute
|
||||
{
|
||||
public bool IsCompatible { get; }
|
||||
|
||||
public Net8CompatibleAttribute(bool isCompatible = true)
|
||||
{
|
||||
IsCompatible = isCompatible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,8 @@ namespace PepperDash.Essentials.Core.Presets
|
||||
var pl = JsonConvert.DeserializeObject<PresetsList>(File.ReadToEnd(_filePath, Encoding.ASCII));
|
||||
Name = pl.Name;
|
||||
PresetsList = pl.Channels;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Loaded {0} presets", PresetsList.Count);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
@@ -17,12 +18,40 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
private List<IEssentialsRoom> _rooms;
|
||||
|
||||
private bool isInAutoMode;
|
||||
public List<IKeyName> Rooms
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rooms.Cast<IKeyName>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isInAutoMode;
|
||||
|
||||
public bool IsInAutoMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isInAutoMode;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == _isInAutoMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isInAutoMode = value;
|
||||
IsInAutoModeFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private CTimer _scenarioChangeDebounceTimer;
|
||||
|
||||
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
||||
|
||||
private Mutex _scenarioChange = new Mutex();
|
||||
|
||||
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
|
||||
: base(key)
|
||||
{
|
||||
@@ -36,14 +65,14 @@ namespace PepperDash.Essentials.Core
|
||||
_scenarioChangeDebounceTimeSeconds = _propertiesConfig.ScenarioChangeDebounceTimeSeconds;
|
||||
}
|
||||
|
||||
IsInAutoModeFeedback = new BoolFeedback(() => isInAutoMode);
|
||||
IsInAutoModeFeedback = new BoolFeedback(() => _isInAutoMode);
|
||||
|
||||
// default to auto mode
|
||||
isInAutoMode = true;
|
||||
IsInAutoMode = true;
|
||||
|
||||
if (_propertiesConfig.defaultToManualMode)
|
||||
{
|
||||
isInAutoMode = false;
|
||||
IsInAutoMode = false;
|
||||
}
|
||||
|
||||
IsInAutoModeFeedback.FireUpdate();
|
||||
@@ -55,8 +84,16 @@ namespace PepperDash.Essentials.Core
|
||||
SetupPartitionStateProviders();
|
||||
|
||||
SetRooms();
|
||||
});
|
||||
|
||||
if (isInAutoMode)
|
||||
|
||||
// Subscribe to the AllDevicesInitialized event
|
||||
// We need to wait until all devices are initialized in case
|
||||
// any actions are dependent on 3rd party devices already being
|
||||
// connected and initialized
|
||||
DeviceManager.AllDevicesInitialized += (o, a) =>
|
||||
{
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
DetermineRoomCombinationScenario();
|
||||
}
|
||||
@@ -64,10 +101,10 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
SetRoomCombinationScenario(_propertiesConfig.defaultScenarioKey);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void CreateScenarios()
|
||||
private void CreateScenarios()
|
||||
{
|
||||
foreach (var scenarioConfig in _propertiesConfig.Scenarios)
|
||||
{
|
||||
@@ -76,21 +113,29 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
void SetRooms()
|
||||
private void SetRooms()
|
||||
{
|
||||
_rooms = new List<IEssentialsRoom>();
|
||||
|
||||
foreach (var roomKey in _propertiesConfig.RoomKeys)
|
||||
{
|
||||
var room = DeviceManager.GetDeviceForKey(roomKey) as IEssentialsRoom;
|
||||
if (room != null)
|
||||
var room = DeviceManager.GetDeviceForKey(roomKey);
|
||||
|
||||
if (DeviceManager.GetDeviceForKey(roomKey) is IEssentialsRoom essentialsRoom)
|
||||
{
|
||||
_rooms.Add(room);
|
||||
_rooms.Add(essentialsRoom);
|
||||
}
|
||||
}
|
||||
|
||||
var rooms = DeviceManager.AllDevices.OfType<IEssentialsRoom>().Cast<Device>();
|
||||
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
room.Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void SetupPartitionStateProviders()
|
||||
private void SetupPartitionStateProviders()
|
||||
{
|
||||
foreach (var pConfig in _propertiesConfig.Partitions)
|
||||
{
|
||||
@@ -104,14 +149,18 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
|
||||
void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
private void PartitionPresentFeedback_OutputChange(object sender, FeedbackEventArgs e)
|
||||
{
|
||||
StartDebounceTimer();
|
||||
}
|
||||
|
||||
void StartDebounceTimer()
|
||||
private void StartDebounceTimer()
|
||||
{
|
||||
var time = _scenarioChangeDebounceTimeSeconds * 1000;
|
||||
// default to 500ms for manual mode
|
||||
var time = 500;
|
||||
|
||||
// if in auto mode, debounce the scenario change
|
||||
if (IsInAutoMode) time = _scenarioChangeDebounceTimeSeconds * 1000;
|
||||
|
||||
if (_scenarioChangeDebounceTimer == null)
|
||||
{
|
||||
@@ -126,7 +175,7 @@ namespace PepperDash.Essentials.Core
|
||||
/// <summary>
|
||||
/// Determines the current room combination scenario based on the state of the partition sensors
|
||||
/// </summary>
|
||||
void DetermineRoomCombinationScenario()
|
||||
private void DetermineRoomCombinationScenario()
|
||||
{
|
||||
if (_scenarioChangeDebounceTimer != null)
|
||||
{
|
||||
@@ -134,14 +183,20 @@ namespace PepperDash.Essentials.Core
|
||||
_scenarioChangeDebounceTimer = null;
|
||||
}
|
||||
|
||||
this.LogInformation("Determining Combination Scenario");
|
||||
|
||||
var currentScenario = RoomCombinationScenarios.FirstOrDefault((s) =>
|
||||
{
|
||||
this.LogDebug("Checking scenario {scenarioKey}", s.Key);
|
||||
// iterate the partition states
|
||||
foreach (var partitionState in s.PartitionStates)
|
||||
{
|
||||
this.LogDebug("checking PartitionState {partitionStateKey}", partitionState.PartitionKey);
|
||||
// get the partition by key
|
||||
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionState.PartitionKey));
|
||||
|
||||
this.LogDebug("Expected State: {partitionPresent} Actual State: {partitionState}", partitionState.PartitionPresent, partition.PartitionPresentFeedback.BoolValue);
|
||||
|
||||
if (partition != null && partitionState.PartitionPresent != partition.PartitionPresentFeedback.BoolValue)
|
||||
{
|
||||
// the partition can't be found or the state doesn't match
|
||||
@@ -154,10 +209,41 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
if (currentScenario != null)
|
||||
{
|
||||
CurrentScenario = currentScenario;
|
||||
this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key);
|
||||
ChangeScenario(currentScenario);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChangeScenario(IRoomCombinationScenario newScenario)
|
||||
{
|
||||
|
||||
|
||||
if (newScenario == _currentScenario)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Deactivate the old scenario first
|
||||
if (_currentScenario != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Deactivating scenario {currentScenario}", this, _currentScenario.Name);
|
||||
await _currentScenario.Deactivate();
|
||||
}
|
||||
|
||||
_currentScenario = newScenario;
|
||||
|
||||
// Activate the new scenario
|
||||
if (_currentScenario != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, $"Current Scenario: {_currentScenario.Name}", this);
|
||||
await _currentScenario.Activate();
|
||||
}
|
||||
|
||||
RoomCombinationScenarioChanged?.Invoke(this, new EventArgs());
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region IEssentialsRoomCombiner Members
|
||||
|
||||
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||
@@ -168,53 +254,42 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
return _currentScenario;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (value != _currentScenario)
|
||||
{
|
||||
// Deactivate the old scenario first
|
||||
if (_currentScenario != null)
|
||||
{
|
||||
_currentScenario.Deactivate();
|
||||
}
|
||||
|
||||
_currentScenario = value;
|
||||
|
||||
// Activate the new scenario
|
||||
if (_currentScenario != null)
|
||||
{
|
||||
_currentScenario.Activate();
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, this, "Current Scenario: {0}", _currentScenario.Name);
|
||||
}
|
||||
|
||||
var handler = RoomCombinationScenarioChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BoolFeedback IsInAutoModeFeedback { get; private set; }
|
||||
|
||||
public void SetAutoMode()
|
||||
{
|
||||
isInAutoMode = true;
|
||||
IsInAutoModeFeedback.FireUpdate();
|
||||
IsInAutoMode = true;
|
||||
|
||||
foreach (var partition in Partitions)
|
||||
{
|
||||
partition.SetAutoMode();
|
||||
}
|
||||
|
||||
DetermineRoomCombinationScenario();
|
||||
}
|
||||
|
||||
public void SetManualMode()
|
||||
{
|
||||
isInAutoMode = false;
|
||||
IsInAutoModeFeedback.FireUpdate();
|
||||
IsInAutoMode = false;
|
||||
|
||||
foreach (var partition in Partitions)
|
||||
{
|
||||
partition.SetManualMode();
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleMode()
|
||||
{
|
||||
isInAutoMode = !isInAutoMode;
|
||||
IsInAutoModeFeedback.FireUpdate();
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
SetManualMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetAutoMode();
|
||||
}
|
||||
}
|
||||
|
||||
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
|
||||
@@ -223,7 +298,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
public void TogglePartitionState(string partitionKey)
|
||||
{
|
||||
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey)) as IPartitionController;
|
||||
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey));
|
||||
|
||||
if (partition != null)
|
||||
{
|
||||
@@ -233,7 +308,7 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
public void SetRoomCombinationScenario(string scenarioKey)
|
||||
{
|
||||
if (isInAutoMode)
|
||||
if (IsInAutoMode)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "Cannot set room combination scenario when in auto mode. Set to auto mode first.");
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
@@ -21,13 +19,21 @@ namespace PepperDash.Essentials.Core
|
||||
/// <summary>
|
||||
/// The current room combination scenario
|
||||
/// </summary>
|
||||
[JsonProperty("currentScenario")]
|
||||
IRoomCombinationScenario CurrentScenario { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, indicates the current mode is auto mode
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
BoolFeedback IsInAutoModeFeedback {get;}
|
||||
|
||||
[JsonProperty("isInAutoMode")]
|
||||
bool IsInAutoMode { get; }
|
||||
|
||||
[JsonProperty("rooms")]
|
||||
List<IKeyName> Rooms { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets auto mode
|
||||
/// </summary>
|
||||
@@ -46,11 +52,13 @@ namespace PepperDash.Essentials.Core
|
||||
/// <summary>
|
||||
/// The available room combinatino scenarios
|
||||
/// </summary>
|
||||
[JsonProperty("roomCombinationScenarios")]
|
||||
List<IRoomCombinationScenario> RoomCombinationScenarios { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The partition
|
||||
/// </summary>
|
||||
[JsonProperty("partitions")]
|
||||
List<IPartitionController> Partitions { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -71,26 +79,32 @@ namespace PepperDash.Essentials.Core
|
||||
/// <summary>
|
||||
/// When true, indicates that this room combination scenario is active
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
BoolFeedback IsActiveFeedback { get; }
|
||||
|
||||
[JsonProperty("isActive")]
|
||||
bool IsActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Activates this room combination scenario
|
||||
/// </summary>
|
||||
void Activate();
|
||||
Task Activate();
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates this room combination scenario
|
||||
/// </summary>
|
||||
void Deactivate();
|
||||
Task Deactivate();
|
||||
|
||||
/// <summary>
|
||||
/// The state of the partitions that would activate this scenario
|
||||
/// </summary>
|
||||
[JsonProperty("partitionStates")]
|
||||
List<PartitionState> PartitionStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The mapping of UIs by key to rooms by key
|
||||
/// </summary>
|
||||
[JsonProperty("uiMap")]
|
||||
Dictionary<string, string> UiMap { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,50 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog.Events;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a room combination scenario
|
||||
/// </summary>
|
||||
public class RoomCombinationScenario: IRoomCombinationScenario
|
||||
public class RoomCombinationScenario : IRoomCombinationScenario, IKeyName
|
||||
{
|
||||
private RoomCombinationScenarioConfig _config;
|
||||
|
||||
[JsonProperty("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("partitionStates")]
|
||||
public List<PartitionState> PartitionStates { get; private set; }
|
||||
|
||||
[JsonProperty("uiMap")]
|
||||
public Dictionary<string, string> UiMap { get; set; }
|
||||
|
||||
private bool _isActive;
|
||||
|
||||
[JsonProperty("isActive")]
|
||||
public bool IsActive
|
||||
{
|
||||
get { return _isActive; }
|
||||
set
|
||||
{
|
||||
if (value == _isActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isActive = value;
|
||||
IsActiveFeedback.FireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public BoolFeedback IsActiveFeedback { get; private set; }
|
||||
|
||||
private List<DeviceActionWrapper> activationActions;
|
||||
@@ -55,36 +70,40 @@ namespace PepperDash.Essentials.Core
|
||||
IsActiveFeedback = new BoolFeedback(() => _isActive);
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
public async Task Activate()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Activating Scenario: '{0}' with {1} action(s) defined", Name, activationActions.Count);
|
||||
this.LogInformation("Activating Scenario {name} with {activationActionCount} action(s) defined", Name, activationActions.Count);
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
if (activationActions != null)
|
||||
{
|
||||
foreach (var action in activationActions)
|
||||
{
|
||||
DeviceJsonApi.DoDeviceAction(action);
|
||||
this.LogInformation("Running Activation action {@action}", action);
|
||||
await DeviceJsonApi.DoDeviceActionAsync(action);
|
||||
}
|
||||
}
|
||||
|
||||
_isActive = true;
|
||||
IsActiveFeedback.FireUpdate();
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
public async Task Deactivate()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Deactivating Scenario: '{0}' with {1} action(s) defined", Name, deactivationActions.Count);
|
||||
this.LogInformation("Deactivating Scenario {name} with {deactivationActionCount} action(s) defined", Name, deactivationActions.Count);
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
if (deactivationActions != null)
|
||||
{
|
||||
foreach (var action in deactivationActions)
|
||||
{
|
||||
DeviceJsonApi.DoDeviceAction(action);
|
||||
this.LogInformation("Running deactivation action {actionDeviceKey}:{actionMethod}", action.DeviceKey, action.MethodName);
|
||||
await DeviceJsonApi.DoDeviceActionAsync(action);
|
||||
}
|
||||
}
|
||||
|
||||
_isActive = false;
|
||||
IsActiveFeedback.FireUpdate();
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace PepperDash.Essentials.Room.Config
|
||||
//switch on emergency type here. Right now only contact and shutdown
|
||||
var e = new EssentialsRoomEmergencyContactClosure(room.Key + "-emergency", props.Emergency, room);
|
||||
DeviceManager.AddDevice(e);
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -198,6 +199,12 @@ namespace PepperDash.Essentials.Room.Config
|
||||
public string SourceListKey { get; set; }
|
||||
[JsonProperty("destinationListKey")]
|
||||
public string DestinationListKey { get; set; }
|
||||
[JsonProperty("audioControlPointListKey")]
|
||||
public string AudioControlPointListKey { get; set; }
|
||||
[JsonProperty("cameraListKey")]
|
||||
public string CameraListKey { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("defaultSourceItem")]
|
||||
public string DefaultSourceItem { get; set; }
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
public class EssentialsRoomEmergencyTriggerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// contact,
|
||||
/// contact,versiport
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
/// <summary>
|
||||
|
||||
@@ -4,12 +4,16 @@ using PepperDash.Essentials.Room.Config;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase
|
||||
public class EssentialsRoomEmergencyContactClosure : EssentialsRoomEmergencyBase, IEssentialsRoomEmergency
|
||||
{
|
||||
public event EventHandler<EventArgs> EmergencyStateChange;
|
||||
|
||||
IEssentialsRoom Room;
|
||||
string Behavior;
|
||||
bool TriggerOnClose;
|
||||
|
||||
public bool InEmergency { get; private set; }
|
||||
|
||||
public EssentialsRoomEmergencyContactClosure(string key, EssentialsRoomEmergencyConfig config, IEssentialsRoom room) :
|
||||
base(key)
|
||||
{
|
||||
@@ -25,14 +29,49 @@ namespace PepperDash.Essentials.Core
|
||||
cs.DigitalInputPorts[portNum].StateChange += EsentialsRoomEmergencyContactClosure_StateChange;
|
||||
}
|
||||
}
|
||||
else if (config.Trigger.Type.Equals("versiport", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var portNum = (uint)config.Trigger.Number;
|
||||
if (portNum <= cs.NumberOfVersiPorts)
|
||||
{
|
||||
cs.VersiPorts[portNum].Register();
|
||||
cs.VersiPorts[portNum].SetVersiportConfiguration(eVersiportConfiguration.DigitalInput);
|
||||
cs.VersiPorts[portNum].DisablePullUpResistor = true;
|
||||
cs.VersiPorts[portNum].VersiportChange += EssentialsRoomEmergencyContactClosure_VersiportChange;
|
||||
}
|
||||
}
|
||||
Behavior = config.Behavior;
|
||||
TriggerOnClose = config.Trigger.TriggerOnClose;
|
||||
}
|
||||
|
||||
private void EssentialsRoomEmergencyContactClosure_VersiportChange(Versiport port, VersiportEventArgs args)
|
||||
{
|
||||
if (args.Event == eVersiportEvent.DigitalInChange)
|
||||
{
|
||||
ContactClosure_StateChange(port.DigitalIn);
|
||||
}
|
||||
}
|
||||
|
||||
void EsentialsRoomEmergencyContactClosure_StateChange(DigitalInput digitalInput, DigitalInputEventArgs args)
|
||||
{
|
||||
if (args.State && TriggerOnClose || !args.State && !TriggerOnClose)
|
||||
ContactClosure_StateChange(args.State);
|
||||
}
|
||||
|
||||
void ContactClosure_StateChange(bool portState)
|
||||
{
|
||||
if (portState && TriggerOnClose || !portState && !TriggerOnClose)
|
||||
{
|
||||
InEmergency = true;
|
||||
if (EmergencyStateChange != null)
|
||||
EmergencyStateChange(this, new EventArgs());
|
||||
RunEmergencyBehavior();
|
||||
}
|
||||
else
|
||||
{
|
||||
InEmergency = false;
|
||||
if (EmergencyStateChange != null)
|
||||
EmergencyStateChange(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,4 +83,14 @@ namespace PepperDash.Essentials.Core
|
||||
Room.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the functionality of a room emergency contact closure
|
||||
/// </summary>
|
||||
public interface IEssentialsRoomEmergency
|
||||
{
|
||||
event EventHandler<EventArgs> EmergencyStateChange;
|
||||
|
||||
bool InEmergency { get; }
|
||||
}
|
||||
}
|
||||
@@ -59,28 +59,102 @@ namespace PepperDash.Essentials.Core
|
||||
/// </summary>
|
||||
public IMobileControlRoomMessenger MobileControlRoomBridge { get; private set; }
|
||||
|
||||
protected const string _defaultListKey = "default";
|
||||
|
||||
/// <summary>
|
||||
/// The config name of the source list
|
||||
/// </summary>
|
||||
///
|
||||
protected string _SourceListKey;
|
||||
private string _sourceListKey;
|
||||
public string SourceListKey {
|
||||
get
|
||||
{
|
||||
return _SourceListKey;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (value != _SourceListKey)
|
||||
if(string.IsNullOrEmpty(_sourceListKey))
|
||||
{
|
||||
_SourceListKey = value;
|
||||
return _defaultListKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _sourceListKey;
|
||||
}
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != _sourceListKey)
|
||||
{
|
||||
_sourceListKey = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DestinationListKey { get; private set; }
|
||||
private string _destinationListKey;
|
||||
public string DestinationListKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_destinationListKey))
|
||||
{
|
||||
return _defaultListKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _destinationListKey;
|
||||
}
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != _destinationListKey)
|
||||
{
|
||||
_destinationListKey = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected const string _defaultSourceListKey = "default";
|
||||
private string _audioControlPointListKey;
|
||||
public string AudioControlPointListKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_audioControlPointListKey))
|
||||
{
|
||||
return _defaultListKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _destinationListKey;
|
||||
}
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != _audioControlPointListKey)
|
||||
{
|
||||
_audioControlPointListKey = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _cameraListKey;
|
||||
public string CameraListKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_cameraListKey))
|
||||
{
|
||||
return _defaultListKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _cameraListKey;
|
||||
}
|
||||
}
|
||||
protected set
|
||||
{
|
||||
if (value != _cameraListKey)
|
||||
{
|
||||
_cameraListKey = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timer used for informing the UIs of a shutdown
|
||||
@@ -141,6 +215,7 @@ namespace PepperDash.Essentials.Core
|
||||
if (!ShutdownPromptTimer.IsRunningFeedback.BoolValue)
|
||||
ShutdownType = eShutdownType.None;
|
||||
};
|
||||
|
||||
ShutdownPromptTimer.HasFinished += (o, a) => Shutdown(); // Shutdown is triggered
|
||||
|
||||
ShutdownPromptSeconds = 60;
|
||||
@@ -191,7 +266,7 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceListKey = _defaultSourceListKey;
|
||||
sourceListKey = _defaultListKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace PepperDash.Essentials.Core
|
||||
|
||||
string DestinationListKey { get; }
|
||||
|
||||
string AudioControlPointListKey { get; }
|
||||
|
||||
string CameraListKey { get; }
|
||||
|
||||
SecondsCountdownTimer ShutdownPromptTimer { get; }
|
||||
int ShutdownPromptSeconds { get; }
|
||||
int ShutdownVacancySeconds { get; }
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace PepperDash.Essentials.Core
|
||||
{
|
||||
void RunRouteAction(string routeKey, string sourceListKey);
|
||||
|
||||
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
|
||||
void RunRouteAction(string routeKey, string sourceListKey, Action successCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,6 +77,34 @@ namespace PepperDash.Essentials.Core
|
||||
SecondsCountdownTimer ShutdownPromptTimer { get; }
|
||||
|
||||
void SetShutdownPromptSeconds(int seconds);
|
||||
|
||||
void StartShutdown(eShutdownType type);
|
||||
}
|
||||
|
||||
/// <summary""'""">
|
||||
/// Describes a room with a tech password
|
||||
/// </summary>
|
||||
public interface ITechPassword
|
||||
{
|
||||
event EventHandler<TechPasswordEventArgs> TechPasswordValidateResult;
|
||||
|
||||
event EventHandler<EventArgs> TechPasswordChanged;
|
||||
|
||||
int TechPasswordLength { get; }
|
||||
|
||||
void ValidateTechPassword(string password);
|
||||
|
||||
void SetTechPassword(string oldPassword, string newPassword);
|
||||
}
|
||||
|
||||
public class TechPasswordEventArgs : EventArgs
|
||||
{
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
public TechPasswordEventArgs(bool isValid)
|
||||
{
|
||||
IsValid = isValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -131,4 +159,13 @@ namespace PepperDash.Essentials.Core
|
||||
Core.Privacy.MicrophonePrivacyController MicrophonePrivacy { get; }
|
||||
}
|
||||
|
||||
public interface IHasAccessoryDevices : IKeyName
|
||||
{
|
||||
List<string> AccessoryDeviceKeys { get; }
|
||||
}
|
||||
|
||||
public interface IHasCiscoNavigatorTouchpanel
|
||||
{
|
||||
string CiscoNavigatorTouchpanelKey { get; }
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
public class DummyRoutingInputsDevice : Device, IRoutingSource
|
||||
public class DummyRoutingInputsDevice : Device, IRoutingSource, IRoutingOutputs
|
||||
{
|
||||
/// <summary>
|
||||
/// A single output port, backplane, audioVideo
|
||||
|
||||
373
src/PepperDash.Essentials.Core/Routing/Extensions.cs
Normal file
373
src/PepperDash.Essentials.Core/Routing/Extensions.cs
Normal file
@@ -0,0 +1,373 @@
|
||||
using PepperDash.Essentials.Core.Queues;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Debug = PepperDash.Core.Debug;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Extensions added to any IRoutingInputs classes to provide discovery-based routing
|
||||
/// on those destinations.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
|
||||
|
||||
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
||||
|
||||
/// <summary>
|
||||
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
|
||||
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
|
||||
/// in RouteDescriptorCollection.DefaultCollection
|
||||
/// </summary>
|
||||
public static void ReleaseAndMakeRoute(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, string destinationPortKey = "", string sourcePortKey = "")
|
||||
{
|
||||
// Remove this line before committing!!!!!
|
||||
var frame = new StackFrame(1, true);
|
||||
Debug.LogMessage(LogEventLevel.Information, "ReleaseAndMakeRoute Called from {method} with params {destinationKey}:{sourceKey}:{signalType}:{destinationPortKey}:{sourcePortKey}", frame.GetMethod().Name, destination.Key, source.Key, signalType.ToString(), destinationPortKey, sourcePortKey);
|
||||
|
||||
var inputPort = string.IsNullOrEmpty(destinationPortKey) ? null : destination.InputPorts.FirstOrDefault(p => p.Key == destinationPortKey);
|
||||
var outputPort = string.IsNullOrEmpty(sourcePortKey) ? null : source.OutputPorts.FirstOrDefault(p => p.Key == sourcePortKey);
|
||||
|
||||
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
|
||||
}
|
||||
public static void ReleaseRoute(this IRoutingInputs destination)
|
||||
{
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty));
|
||||
}
|
||||
|
||||
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
|
||||
{
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey));
|
||||
}
|
||||
|
||||
public static void RemoveRouteRequestForDestination(string destinationKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
|
||||
|
||||
var result = RouteRequests.Remove(destinationKey);
|
||||
|
||||
var messageTemplate = result ? "Route Request for {destination} removed" : "Route Request for {destination} not found";
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, messageTemplate, null, destinationKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
|
||||
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
|
||||
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
|
||||
/// of an audio/video route are discovered a route descriptor is returned. If no route is
|
||||
/// discovered, then null is returned
|
||||
/// </summary>
|
||||
public static (RouteDescriptor, RouteDescriptor) GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
|
||||
{
|
||||
// if it's a single signal type, find the route
|
||||
if (!signalType.HasFlag(eRoutingSignalType.AudioVideo))
|
||||
{
|
||||
var singleTypeRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, signalType);
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key, signalType);
|
||||
|
||||
if (!destination.GetRouteToSource(source, null, null, signalType, 0, singleTypeRouteDescriptor, destinationPort, sourcePort))
|
||||
singleTypeRouteDescriptor = null;
|
||||
|
||||
var routes = singleTypeRouteDescriptor?.Routes ?? new List<RouteSwitchDescriptor>();
|
||||
foreach (var route in routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Route for device: {route}", destination, route.ToString());
|
||||
}
|
||||
|
||||
return (singleTypeRouteDescriptor, null);
|
||||
}
|
||||
// otherwise, audioVideo needs to be handled as two steps.
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Attempting to build source route from {sourceKey} of type {type}", destination, source.Key);
|
||||
|
||||
var audioRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Audio);
|
||||
|
||||
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, audioRouteDescriptor, destinationPort, sourcePort);
|
||||
|
||||
if (!audioSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Cannot find audio route to {0}", destination, source.Key);
|
||||
|
||||
var videoRouteDescriptor = new RouteDescriptor(source, destination, destinationPort, eRoutingSignalType.Video);
|
||||
|
||||
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, videoRouteDescriptor, destinationPort, sourcePort);
|
||||
|
||||
if (!videoSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Cannot find video route to {0}", destination, source.Key);
|
||||
|
||||
foreach (var route in audioRouteDescriptor.Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Audio route for device: {route}", destination, route.ToString());
|
||||
}
|
||||
|
||||
foreach (var route in videoRouteDescriptor.Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Video route for device: {route}", destination, route.ToString());
|
||||
}
|
||||
|
||||
|
||||
if (!audioSuccess && !videoSuccess)
|
||||
return (null, null);
|
||||
|
||||
|
||||
return (audioRouteDescriptor, videoRouteDescriptor);
|
||||
}
|
||||
|
||||
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
|
||||
{
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (destinationPort == null) Debug.LogMessage(LogEventLevel.Information, "Destination port is null");
|
||||
if (sourcePort == null) Debug.LogMessage(LogEventLevel.Information, "Source port is null");
|
||||
|
||||
var routeRequest = new RouteRequest
|
||||
{
|
||||
Destination = destination,
|
||||
DestinationPort = destinationPort,
|
||||
Source = source,
|
||||
SourcePort = sourcePort,
|
||||
SignalType = signalType
|
||||
};
|
||||
|
||||
var coolingDevice = destination as IWarmingCooling;
|
||||
|
||||
//We already have a route request for this device, and it's a cooling device and is cooling
|
||||
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
|
||||
RouteRequests[destination.Key] = routeRequest;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down and already has a routing request stored. Storing new route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//New Request
|
||||
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
|
||||
RouteRequests.Add(destination.Key, routeRequest);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is cooling down. Storing route request to route to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
|
||||
{
|
||||
var handledRequest = RouteRequests[destination.Key];
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= handledRequest.HandleCooldown;
|
||||
|
||||
RouteRequests.Remove(destination.Key);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||
}
|
||||
|
||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty));
|
||||
|
||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||
}
|
||||
|
||||
private static void RunRouteRequest(RouteRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.Source == null)
|
||||
return;
|
||||
|
||||
var (audioOrSingleRoute, videoRoute) = request.Destination.GetRouteToSource(request.Source, request.SignalType, request.DestinationPort, request.SourcePort);
|
||||
|
||||
if (audioOrSingleRoute == null && videoRoute == null)
|
||||
return;
|
||||
|
||||
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(audioOrSingleRoute);
|
||||
|
||||
if (videoRoute != null)
|
||||
{
|
||||
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(videoRoute);
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Executing full route", request.Destination);
|
||||
|
||||
audioOrSingleRoute.ExecuteRoutes();
|
||||
videoRoute?.ExecuteRoutes();
|
||||
} catch(Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception Running Route Request {request}", null, request);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will release the existing route on the destination, if it is found in
|
||||
/// RouteDescriptorCollection.DefaultCollection
|
||||
/// </summary>
|
||||
/// <param name="destination"></param>
|
||||
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Release route for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
|
||||
if (RouteRequests.TryGetValue(destination.Key, out RouteRequest existingRequest) && destination is IWarmingCooling)
|
||||
{
|
||||
var coolingDevice = destination as IWarmingCooling;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
|
||||
}
|
||||
|
||||
RouteRequests.Remove(destination.Key);
|
||||
|
||||
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination, inputPortKey);
|
||||
if (current != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
||||
current.ReleaseRoutes();
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception releasing route for '{destination}':'{inputPortKey}'",null, destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The recursive part of this. Will stop on each device, search its inputs for the
|
||||
/// desired source and if not found, invoke this function for the each input port
|
||||
/// hoping to find the source.
|
||||
/// </summary>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="destinationPort">The RoutingOutputPort whose link is being checked for a route</param>
|
||||
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
|
||||
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
|
||||
/// <param name="cycle">Just an informational counter</param>
|
||||
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
|
||||
/// <returns>true if source is hit</returns>
|
||||
private static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
|
||||
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
|
||||
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable, RoutingInputPort destinationPort, RoutingOutputPort sourcePort)
|
||||
{
|
||||
cycle++;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {cycle} {sourceKey}:{sourcePortKey}--> {destinationKey}:{destinationPortKey} {type}", null, cycle, source.Key, sourcePort?.Key ?? "auto", destination.Key, destinationPort?.Key ?? "auto", signalType.ToString());
|
||||
|
||||
RoutingInputPort goodInputPort = null;
|
||||
|
||||
IEnumerable<TieLine> destinationTieLines;
|
||||
TieLine directTie = null;
|
||||
|
||||
if (destinationPort == null)
|
||||
{
|
||||
|
||||
destinationTieLines = TieLineCollection.Default.Where(t =>
|
||||
t.DestinationPort.ParentDevice.Key == destination.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
|
||||
}
|
||||
else
|
||||
{
|
||||
destinationTieLines = TieLineCollection.Default.Where(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && (t.Type == signalType || t.Type.HasFlag(eRoutingSignalType.AudioVideo)));
|
||||
}
|
||||
|
||||
// find the TieLine without a port
|
||||
if (destinationPort == null && sourcePort == null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||
}
|
||||
// find a tieLine to a specific destination port without a specific source port
|
||||
else if (destinationPort != null && sourcePort == null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key);
|
||||
}
|
||||
// find a tieline to a specific source port without a specific destination port
|
||||
else if (destinationPort == null & sourcePort != null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
}
|
||||
// find a tieline to a specific source port and destination port
|
||||
else if (destinationPort != null && sourcePort != null)
|
||||
{
|
||||
directTie = destinationTieLines.FirstOrDefault(t => t.DestinationPort.ParentDevice.Key == destination.Key && t.DestinationPort.Key == destinationPort.Key && t.SourcePort.ParentDevice.Key == source.Key && t.SourcePort.Key == sourcePort.Key);
|
||||
}
|
||||
|
||||
if (directTie != null) // Found a tie directly to the source
|
||||
{
|
||||
goodInputPort = directTie.DestinationPort;
|
||||
}
|
||||
else // no direct-connect. Walk back devices.
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "is not directly connected to {sourceKey}. Walking down tie lines", destination, source.Key);
|
||||
|
||||
// No direct tie? Run back out on the inputs' attached devices...
|
||||
// Only the ones that are routing devices
|
||||
var midpointTieLines = destinationTieLines.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
|
||||
|
||||
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
|
||||
if (alreadyCheckedDevices == null)
|
||||
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
|
||||
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
|
||||
|
||||
foreach (var tieLine in midpointTieLines)
|
||||
{
|
||||
var midpointDevice = tieLine.SourcePort.ParentDevice as IRoutingInputsOutputs;
|
||||
|
||||
// Check if this previous device has already been walked
|
||||
if (alreadyCheckedDevices.Contains(midpointDevice))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Skipping input {midpointDeviceKey} on {destinationKey}, this was already checked", destination, midpointDevice.Key, destination.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var midpointOutputPort = tieLine.SourcePort;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Trying to find route on {midpointDeviceKey}", destination, midpointDevice.Key);
|
||||
|
||||
// haven't seen this device yet. Do it. Pass the output port to the next
|
||||
// level to enable switching on success
|
||||
var upstreamRoutingSuccess = midpointDevice.GetRouteToSource(source, midpointOutputPort,
|
||||
alreadyCheckedDevices, signalType, cycle, routeTable, null, sourcePort);
|
||||
|
||||
if (upstreamRoutingSuccess)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Upstream device route found", destination);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Route found on {midpointDeviceKey}", destination, midpointDevice.Key);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "TieLine: SourcePort: {SourcePort} DestinationPort: {DestinationPort}", destination, tieLine.SourcePort, tieLine.DestinationPort);
|
||||
goodInputPort = tieLine.DestinationPort;
|
||||
break; // Stop looping the inputs in this cycle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (goodInputPort == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "No route found to {0}", destination, source.Key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// we have a route on corresponding inputPort. *** Do the route ***
|
||||
|
||||
if (destination is IRoutingSink)
|
||||
{
|
||||
// it's a sink device
|
||||
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
|
||||
}
|
||||
else if (destination is IRouting)
|
||||
{
|
||||
routeTable.Routes.Add(new RouteSwitchDescriptor(outputPortToUse, goodInputPort));
|
||||
}
|
||||
else // device is merely IRoutingInputOutputs
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "No routing. Passthrough device", destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Unmerged change from project 'PepperDash.Essentials.Core (net6)'
|
||||
Before:
|
||||
namespace PepperDash.Essentials.Core.Routing.Interfaces
|
||||
After:
|
||||
using PepperDash;
|
||||
using PepperDash.Essentials;
|
||||
using PepperDash.Essentials.Core;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using PepperDash.Essentials.Core.Routing.Interfaces
|
||||
*/
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// The handler type for a Room's SourceInfoChange
|
||||
/// </summary>
|
||||
public delegate void SourceInfoChangeHandler(SourceListItem info, ChangeType type);
|
||||
//*******************************************************************************************
|
||||
// Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// For rooms with a single presentation source, change event
|
||||
/// </summary>
|
||||
public interface IHasCurrentSourceInfoChange
|
||||
{
|
||||
string CurrentSourceInfoKey { get; set; }
|
||||
SourceListItem CurrentSourceInfo { get; set; }
|
||||
event SourceInfoChangeHandler CurrentSourceChange;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
public interface IVideoSync: IKeyed
|
||||
public interface IVideoSync : IKeyed
|
||||
{
|
||||
bool VideoSyncDetected { get; }
|
||||
|
||||
|
||||
10
src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs
Normal file
10
src/PepperDash.Essentials.Core/Routing/IRmcRouting.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C)
|
||||
/// </summary>
|
||||
public interface IRmcRouting : IRoutingNumeric
|
||||
{
|
||||
IntFeedback AudioVideoSourceNumericFeedback { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an IRmcRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface IRmcRoutingWithFeedback : IRmcRouting
|
||||
{
|
||||
}
|
||||
}
|
||||
21
src/PepperDash.Essentials.Core/Routing/IRouting.cs
Normal file
21
src/PepperDash.Essentials.Core/Routing/IRouting.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a midpoint device as have internal routing. Any devices in the middle of the
|
||||
/// signal chain, that do switching, must implement this for routing to work otherwise
|
||||
/// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough
|
||||
/// device.
|
||||
/// </summary>
|
||||
public interface IRouting : IRoutingInputsOutputs
|
||||
{
|
||||
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
|
||||
}
|
||||
|
||||
/*public interface IRouting<TInputSelector,TOutputSelector> : IRoutingInputsOutputs
|
||||
{
|
||||
void ExecuteSwitch(TInputSelector inputSelector, TOutputSelector outputSelector, eRoutingSignalType signalType);
|
||||
}*/
|
||||
}
|
||||
16
src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs
Normal file
16
src/PepperDash.Essentials.Core/Routing/IRoutingFeedback.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an event structure for reporting output route data
|
||||
/// </summary>
|
||||
public interface IRoutingFeedback : IKeyName
|
||||
{
|
||||
event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
|
||||
//void OnSwitchChange(RoutingNumericEventArgs e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
|
||||
public interface IRoutingHasVideoInputSyncFeedbacks
|
||||
{
|
||||
FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; }
|
||||
}
|
||||
}
|
||||
18
src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs
Normal file
18
src/PepperDash.Essentials.Core/Routing/IRoutingInputs.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a class that has a collection of RoutingInputPorts
|
||||
/// </summary>
|
||||
public interface IRoutingInputs : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingInputPort> InputPorts { get; }
|
||||
}
|
||||
|
||||
/* public interface IRoutingInputs<TSelector> : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingInputPort<TSelector>, TSelector> InputPorts { get; }
|
||||
}*/
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class RouteRequest
|
||||
{
|
||||
public IRoutingSink Destination {get; set;}
|
||||
public IRoutingOutputs Source {get; set;}
|
||||
public eRoutingSignalType SignalType {get; set;}
|
||||
|
||||
public void HandleCooldown(object sender, FeedbackEventArgs args)
|
||||
{
|
||||
var coolingDevice = sender as IWarmingCooling;
|
||||
|
||||
if(args.BoolValue == false)
|
||||
{
|
||||
Destination.ReleaseAndMakeRoute(Source, SignalType);
|
||||
|
||||
if(sender == null) return;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extensions added to any IRoutingInputs classes to provide discovery-based routing
|
||||
/// on those destinations.
|
||||
/// </summary>
|
||||
public static class IRoutingInputsExtensions
|
||||
{
|
||||
private static Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
|
||||
/// <summary>
|
||||
/// Gets any existing RouteDescriptor for a destination, clears it using ReleaseRoute
|
||||
/// and then attempts a new Route and if sucessful, stores that RouteDescriptor
|
||||
/// in RouteDescriptorCollection.DefaultCollection
|
||||
/// </summary>
|
||||
public static void ReleaseAndMakeRoute(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
|
||||
{
|
||||
var routeRequest = new RouteRequest {
|
||||
Destination = destination,
|
||||
Source = source,
|
||||
SignalType = signalType
|
||||
};
|
||||
|
||||
var coolingDevice = destination as IWarmingCooling;
|
||||
|
||||
RouteRequest existingRouteRequest;
|
||||
|
||||
//We already have a route request for this device, and it's a cooling device and is cooling
|
||||
if (RouteRequests.TryGetValue(destination.Key, out existingRouteRequest) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRouteRequest.HandleCooldown;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
|
||||
RouteRequests[destination.Key] = routeRequest;
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down and already has a routing request stored. Storing new route request to route to source key: {1}", destination.Key, routeRequest.Source.Key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//New Request
|
||||
if (coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == true)
|
||||
{
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= routeRequest.HandleCooldown;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange += routeRequest.HandleCooldown;
|
||||
|
||||
RouteRequests.Add(destination.Key, routeRequest);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is cooling down. Storing route request to route to source key: {1}", destination.Key, routeRequest.Source.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RouteRequests.ContainsKey(destination.Key) && coolingDevice != null && coolingDevice.IsCoolingDownFeedback.BoolValue == false)
|
||||
{
|
||||
RouteRequests.Remove(destination.Key);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "******************************************************** Device: {0} is NOT cooling down. Removing stored route request and routing to source key: {1}", destination.Key, routeRequest.Source.Key);
|
||||
}
|
||||
|
||||
destination.ReleaseRoute();
|
||||
|
||||
RunRouteRequest(routeRequest);
|
||||
}
|
||||
|
||||
public static void RunRouteRequest(RouteRequest request)
|
||||
{
|
||||
if (request.Source == null) return;
|
||||
var newRoute = request.Destination.GetRouteToSource(request.Source, request.SignalType);
|
||||
if (newRoute == null) return;
|
||||
RouteDescriptorCollection.DefaultCollection.AddRouteDescriptor(newRoute);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, request.Destination, "Executing full route");
|
||||
newRoute.ExecuteRoutes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will release the existing route on the destination, if it is found in
|
||||
/// RouteDescriptorCollection.DefaultCollection
|
||||
/// </summary>
|
||||
/// <param name="destination"></param>
|
||||
public static void ReleaseRoute(this IRoutingSink destination)
|
||||
{
|
||||
RouteRequest existingRequest;
|
||||
|
||||
if (RouteRequests.TryGetValue(destination.Key, out existingRequest) && destination is IWarmingCooling)
|
||||
{
|
||||
var coolingDevice = destination as IWarmingCooling;
|
||||
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= existingRequest.HandleCooldown;
|
||||
}
|
||||
|
||||
RouteRequests.Remove(destination.Key);
|
||||
|
||||
var current = RouteDescriptorCollection.DefaultCollection.RemoveRouteDescriptor(destination);
|
||||
if (current != null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, destination, "Releasing current route: {0}", current.Source.Key);
|
||||
current.ReleaseRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a RouteDescriptor that contains the steps necessary to make a route between devices.
|
||||
/// Routes of type AudioVideo will be built as two separate routes, audio and video. If
|
||||
/// a route is discovered, a new RouteDescriptor is returned. If one or both parts
|
||||
/// of an audio/video route are discovered a route descriptor is returned. If no route is
|
||||
/// discovered, then null is returned
|
||||
/// </summary>
|
||||
public static RouteDescriptor GetRouteToSource(this IRoutingSink destination, IRoutingOutputs source, eRoutingSignalType signalType)
|
||||
{
|
||||
var routeDescr = new RouteDescriptor(source, destination, signalType);
|
||||
// if it's a single signal type, find the route
|
||||
if ((signalType & (eRoutingSignalType.Audio & eRoutingSignalType.Video)) == (eRoutingSignalType.Audio & eRoutingSignalType.Video))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build source route from {0}", source.Key);
|
||||
if (!destination.GetRouteToSource(source, null, null, signalType, 0, routeDescr))
|
||||
routeDescr = null;
|
||||
}
|
||||
// otherwise, audioVideo needs to be handled as two steps.
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, destination, "Attempting to build audio and video routes from {0}", source.Key);
|
||||
var audioSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Audio, 0, routeDescr);
|
||||
if (!audioSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find audio route to {0}", source.Key);
|
||||
var videoSuccess = destination.GetRouteToSource(source, null, null, eRoutingSignalType.Video, 0, routeDescr);
|
||||
if (!videoSuccess)
|
||||
Debug.LogMessage(LogEventLevel.Debug, destination, "Cannot find video route to {0}", source.Key);
|
||||
if (!audioSuccess && !videoSuccess)
|
||||
routeDescr = null;
|
||||
}
|
||||
|
||||
//Debug.LogMessage(LogEventLevel.Debug, destination, "Route{0} discovered", routeDescr == null ? " NOT" : "");
|
||||
return routeDescr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The recursive part of this. Will stop on each device, search its inputs for the
|
||||
/// desired source and if not found, invoke this function for the each input port
|
||||
/// hoping to find the source.
|
||||
/// </summary>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="outputPortToUse">The RoutingOutputPort whose link is being checked for a route</param>
|
||||
/// <param name="alreadyCheckedDevices">Prevents Devices from being twice-checked</param>
|
||||
/// <param name="signalType">This recursive function should not be called with AudioVideo</param>
|
||||
/// <param name="cycle">Just an informational counter</param>
|
||||
/// <param name="routeTable">The RouteDescriptor being populated as the route is discovered</param>
|
||||
/// <returns>true if source is hit</returns>
|
||||
static bool GetRouteToSource(this IRoutingInputs destination, IRoutingOutputs source,
|
||||
RoutingOutputPort outputPortToUse, List<IRoutingInputsOutputs> alreadyCheckedDevices,
|
||||
eRoutingSignalType signalType, int cycle, RouteDescriptor routeTable)
|
||||
{
|
||||
cycle++;
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "GetRouteToSource: {0} {1}--> {2}", cycle, source.Key, destination.Key);
|
||||
|
||||
RoutingInputPort goodInputPort = null;
|
||||
var destDevInputTies = TieLineCollection.Default.Where(t =>
|
||||
t.DestinationPort.ParentDevice == destination && (t.Type == signalType || (t.Type & (eRoutingSignalType.Audio | eRoutingSignalType.Video)) == (eRoutingSignalType.Audio | eRoutingSignalType.Video)));
|
||||
|
||||
// find a direct tie
|
||||
var directTie = destDevInputTies.FirstOrDefault(
|
||||
t => t.DestinationPort.ParentDevice == destination
|
||||
&& t.SourcePort.ParentDevice == source);
|
||||
if (directTie != null) // Found a tie directly to the source
|
||||
{
|
||||
goodInputPort = directTie.DestinationPort;
|
||||
}
|
||||
else // no direct-connect. Walk back devices.
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, "is not directly connected to {0}. Walking down tie lines", source.Key);
|
||||
|
||||
// No direct tie? Run back out on the inputs' attached devices...
|
||||
// Only the ones that are routing devices
|
||||
var attachedMidpoints = destDevInputTies.Where(t => t.SourcePort.ParentDevice is IRoutingInputsOutputs);
|
||||
|
||||
//Create a list for tracking already checked devices to avoid loops, if it doesn't already exist from previous iteration
|
||||
if (alreadyCheckedDevices == null)
|
||||
alreadyCheckedDevices = new List<IRoutingInputsOutputs>();
|
||||
alreadyCheckedDevices.Add(destination as IRoutingInputsOutputs);
|
||||
|
||||
foreach (var inputTieToTry in attachedMidpoints)
|
||||
{
|
||||
var upstreamDeviceOutputPort = inputTieToTry.SourcePort;
|
||||
var upstreamRoutingDevice = upstreamDeviceOutputPort.ParentDevice as IRoutingInputsOutputs;
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, "Trying to find route on {0}", upstreamRoutingDevice.Key);
|
||||
|
||||
// Check if this previous device has already been walked
|
||||
if (alreadyCheckedDevices.Contains(upstreamRoutingDevice))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, "Skipping input {0} on {1}, this was already checked", upstreamRoutingDevice.Key, destination.Key);
|
||||
continue;
|
||||
}
|
||||
// haven't seen this device yet. Do it. Pass the output port to the next
|
||||
// level to enable switching on success
|
||||
var upstreamRoutingSuccess = upstreamRoutingDevice.GetRouteToSource(source, upstreamDeviceOutputPort,
|
||||
alreadyCheckedDevices, signalType, cycle, routeTable);
|
||||
if (upstreamRoutingSuccess)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, "Upstream device route found");
|
||||
goodInputPort = inputTieToTry.DestinationPort;
|
||||
break; // Stop looping the inputs in this cycle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have a route on corresponding inputPort. *** Do the route ***
|
||||
if (goodInputPort != null)
|
||||
{
|
||||
//Debug.LogMessage(LogEventLevel.Verbose, destination, "adding RouteDescriptor");
|
||||
if (outputPortToUse == null)
|
||||
{
|
||||
// it's a sink device
|
||||
routeTable.Routes.Add(new RouteSwitchDescriptor(goodInputPort));
|
||||
}
|
||||
else if (destination is IRouting)
|
||||
{
|
||||
routeTable.Routes.Add(new RouteSwitchDescriptor (outputPortToUse, goodInputPort));
|
||||
}
|
||||
else // device is merely IRoutingInputOutputs
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, " No routing. Passthrough device");
|
||||
//Debug.LogMessage(LogEventLevel.Verbose, destination, "Exiting cycle {0}", cycle);
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, destination, "No route found to {0}", source.Key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE MOVE
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
|
||||
/// </summary>
|
||||
public class RouteDescriptorCollection
|
||||
{
|
||||
public static RouteDescriptorCollection DefaultCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DefaultCollection == null)
|
||||
_DefaultCollection = new RouteDescriptorCollection();
|
||||
return _DefaultCollection;
|
||||
}
|
||||
}
|
||||
static RouteDescriptorCollection _DefaultCollection;
|
||||
|
||||
List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
||||
/// destination exists already, it will not be added - in order to preserve
|
||||
/// proper route releasing.
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
public void AddRouteDescriptor(RouteDescriptor descriptor)
|
||||
{
|
||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
||||
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
|
||||
return;
|
||||
}
|
||||
RouteDescriptors.Add(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RouteDescriptor for a destination
|
||||
/// </summary>
|
||||
/// <returns>null if no RouteDescriptor for a destination exists</returns>
|
||||
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination)
|
||||
{
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
|
||||
/// Returns null if no route with the provided destination exists.
|
||||
/// </summary>
|
||||
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination)
|
||||
{
|
||||
var descr = GetRouteDescriptorForDestination(destination);
|
||||
if (descr != null)
|
||||
RouteDescriptors.Remove(descr);
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an collection of individual route steps between Source and Destination
|
||||
/// </summary>
|
||||
public class RouteDescriptor
|
||||
{
|
||||
public IRoutingInputs Destination { get; private set; }
|
||||
public IRoutingOutputs Source { get; private set; }
|
||||
public eRoutingSignalType SignalType { get; private set; }
|
||||
public List<RouteSwitchDescriptor> Routes { get; private set; }
|
||||
|
||||
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType)
|
||||
{
|
||||
Destination = destination;
|
||||
Source = source;
|
||||
SignalType = signalType;
|
||||
Routes = new List<RouteSwitchDescriptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all routes described in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ExecuteRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", route.ToString());
|
||||
if (route.SwitchingDevice is IRoutingSink)
|
||||
{
|
||||
var device = route.SwitchingDevice as IRoutingSinkWithSwitching;
|
||||
if (device == null)
|
||||
continue;
|
||||
|
||||
device.ExecuteSwitch(route.InputPort.Selector);
|
||||
}
|
||||
else if (route.SwitchingDevice is IRouting)
|
||||
{
|
||||
(route.SwitchingDevice as IRouting).ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
|
||||
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all routes in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ReleaseRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
if (route.SwitchingDevice is IRouting)
|
||||
{
|
||||
// Pull the route from the port. Whatever is watching the output's in use tracker is
|
||||
// responsible for responding appropriately.
|
||||
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
||||
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an individual link for a route
|
||||
/// </summary>
|
||||
public class RouteSwitchDescriptor
|
||||
{
|
||||
public IRoutingInputs SwitchingDevice { get { return InputPort.ParentDevice; } }
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
|
||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
}
|
||||
|
||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
OutputPort = outputPort;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if(SwitchingDevice is IRouting)
|
||||
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
|
||||
else
|
||||
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// For devices like RMCs, baluns, other devices with no switching.
|
||||
/// </summary>
|
||||
public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs
|
||||
{
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// For devices like RMCs, baluns, other devices with no switching.
|
||||
/// </summary>
|
||||
public interface IRoutingInputsOutputs<TInputSelector, TOutputSelector> : IRoutingInputs<TInputSelector>, IRoutingOutputs<TOutputSelector>
|
||||
{
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public interface IRoutingNumeric : IRouting
|
||||
{
|
||||
void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an IRoutingNumeric with a feedback event
|
||||
/// </summary>
|
||||
public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback
|
||||
{
|
||||
}
|
||||
}
|
||||
19
src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs
Normal file
19
src/PepperDash.Essentials.Core/Routing/IRoutingOutputs.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a class that has a collection of RoutingOutputPorts
|
||||
/// </summary>
|
||||
|
||||
public interface IRoutingOutputs : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingOutputPort> OutputPorts { get; }
|
||||
}
|
||||
|
||||
/* public interface IRoutingOutputs<TSelector> : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingOutputPort<TSelector>, TSelector> OutputPorts { get; }
|
||||
}*/
|
||||
}
|
||||
23
src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs
Normal file
23
src/PepperDash.Essentials.Core/Routing/IRoutingSink.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// For fixed-source endpoint devices
|
||||
/// </summary>
|
||||
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
|
||||
{
|
||||
}
|
||||
|
||||
public interface IRoutingSinkWithInputPort :IRoutingSink
|
||||
{
|
||||
RoutingInputPort CurrentInputPort { get; }
|
||||
}
|
||||
/*/// <summary>
|
||||
/// For fixed-source endpoint devices
|
||||
/// </summary>
|
||||
public interface IRoutingSink<TSelector> : IRoutingInputs<TSelector>, IHasCurrentSourceInfoChange
|
||||
{
|
||||
void UpdateRouteRequest<TOutputSelector>(RouteRequest<TSelector, TOutputSelector> request);
|
||||
|
||||
RouteRequest<TSelector, TOutputSelector> GetRouteRequest<TOutputSelector>();
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using PepperDash.Essentials.Core.Routing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// For fixed-source endpoint devices
|
||||
/// </summary>
|
||||
public interface IRoutingSinkWithFeedback : IRoutingSinkWithSwitching
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// For fixed-source endpoint devices
|
||||
/// </summary>
|
||||
public interface IRoutingSinkWithFeedback<TSelector> : IRoutingSinkWithSwitching<TSelector>
|
||||
{
|
||||
RouteSwitchDescriptor CurrentRoute { get; }
|
||||
|
||||
event EventHandler InputChanged;
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public delegate void InputChangedEventHandler(IRoutingSinkWithSwitching destination, RoutingInputPort currentPort);
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint device like a display, that selects inputs
|
||||
/// </summary>
|
||||
public interface IRoutingSinkWithSwitching : IRoutingSink
|
||||
{
|
||||
void ExecuteSwitch(object inputSelector);
|
||||
}
|
||||
|
||||
public interface IRoutingSinkWithSwitchingWithInputPort:IRoutingSinkWithSwitching, IRoutingSinkWithInputPort
|
||||
{
|
||||
event InputChangedEventHandler InputChanged;
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// Endpoint device like a display, that selects inputs
|
||||
/// </summary>
|
||||
public interface IRoutingSinkWithSwitching<TSelector> : IRoutingSink<TSelector>
|
||||
{
|
||||
void ExecuteSwitch(TSelector inputSelector);
|
||||
}*/
|
||||
}
|
||||
9
src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs
Normal file
9
src/PepperDash.Essentials.Core/Routing/IRoutingSource.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
|
||||
/// </summary>
|
||||
public interface IRoutingSource : IRoutingOutputs
|
||||
{
|
||||
}
|
||||
}
|
||||
12
src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs
Normal file
12
src/PepperDash.Essentials.Core/Routing/IRoutingWithClear.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public interface IRoutingWithClear : IRouting
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears a route to an output, however a device needs to do that
|
||||
/// </summary>
|
||||
/// <param name="outputSelector">Output to clear</param>
|
||||
/// <param name="signalType">signal type to clear</param>
|
||||
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
|
||||
/// <summary>
|
||||
/// Defines an IRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface IRoutingWithFeedback : IRouting
|
||||
{
|
||||
List<RouteSwitchDescriptor> CurrentRoutes { get; }
|
||||
|
||||
event RouteChangedEventHandler RouteChanged;
|
||||
}
|
||||
}
|
||||
8
src/PepperDash.Essentials.Core/Routing/ITxRouting.cs
Normal file
8
src/PepperDash.Essentials.Core/Routing/ITxRouting.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public interface ITxRouting : IRoutingNumeric
|
||||
{
|
||||
IntFeedback VideoSourceNumericFeedback { get; }
|
||||
IntFeedback AudioSourceNumericFeedback { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines an IRmcRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface ITxRoutingWithFeedback : ITxRouting
|
||||
{
|
||||
}
|
||||
}
|
||||
170
src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs
Normal file
170
src/PepperDash.Essentials.Core/Routing/RouteDescriptor.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharpPro;
|
||||
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an collection of individual route steps between Source and Destination
|
||||
/// </summary>
|
||||
public class RouteDescriptor
|
||||
{
|
||||
public IRoutingInputs Destination { get; private set; }
|
||||
|
||||
public RoutingInputPort InputPort { get; private set; }
|
||||
|
||||
public IRoutingOutputs Source { get; private set; }
|
||||
public eRoutingSignalType SignalType { get; private set; }
|
||||
public List<RouteSwitchDescriptor> Routes { get; private set; }
|
||||
|
||||
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
|
||||
{
|
||||
Destination = destination;
|
||||
Source = source;
|
||||
SignalType = signalType;
|
||||
InputPort = inputPort;
|
||||
Routes = new List<RouteSwitchDescriptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all routes described in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ExecuteRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
|
||||
|
||||
if (route.SwitchingDevice is IRoutingSinkWithSwitching sink)
|
||||
{
|
||||
sink.ExecuteSwitch(route.InputPort.Selector);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||
{
|
||||
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
|
||||
|
||||
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all routes in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ReleaseRoutes()
|
||||
{
|
||||
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
||||
{
|
||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||
{
|
||||
if (route.OutputPort == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (route.OutputPort.InUseTracker != null)
|
||||
{
|
||||
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Error, "InUseTracker is null for OutputPort {0}", null, route.OutputPort.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
||||
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// Represents an collection of individual route steps between Source and Destination
|
||||
/// </summary>
|
||||
public class RouteDescriptor<TInputSelector, TOutputSelector>
|
||||
{
|
||||
public IRoutingInputs<TInputSelector> Destination { get; private set; }
|
||||
public IRoutingOutputs<TOutputSelector> Source { get; private set; }
|
||||
public eRoutingSignalType SignalType { get; private set; }
|
||||
public List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>> Routes { get; private set; }
|
||||
|
||||
|
||||
public RouteDescriptor(IRoutingOutputs<TOutputSelector> source, IRoutingInputs<TInputSelector> destination, eRoutingSignalType signalType)
|
||||
{
|
||||
Destination = destination;
|
||||
Source = source;
|
||||
SignalType = signalType;
|
||||
Routes = new List<RouteSwitchDescriptor<TInputSelector, TOutputSelector>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes all routes described in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ExecuteRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "ExecuteRoutes: {0}", null, route.ToString());
|
||||
|
||||
if (route.SwitchingDevice is IRoutingSinkWithSwitching<TInputSelector> sink)
|
||||
{
|
||||
sink.ExecuteSwitch(route.InputPort.Selector);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||
{
|
||||
switchingDevice.ExecuteSwitch(route.InputPort.Selector, route.OutputPort.Selector, SignalType);
|
||||
|
||||
route.OutputPort.InUseTracker.AddUser(Destination, "destination-" + SignalType);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Output port {0} routing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all routes in this collection. Typically called via
|
||||
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||
/// </summary>
|
||||
public void ReleaseRoutes()
|
||||
{
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
if (route.SwitchingDevice is IRouting<TInputSelector, TOutputSelector>)
|
||||
{
|
||||
// Pull the route from the port. Whatever is watching the output's in use tracker is
|
||||
// responsible for responding appropriately.
|
||||
route.OutputPort.InUseTracker.RemoveUser(Destination, "destination-" + SignalType);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "Port {0} releasing. Count={1}", null, route.OutputPort.Key, route.OutputPort.InUseTracker.InUseCountFeedback.UShortValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
||||
return string.Format("Route table from {0} to {1}:\r{2}", Source.Key, Destination.Key, string.Join("\r", routesText));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
|
||||
/// </summary>
|
||||
public class RouteDescriptorCollection
|
||||
{
|
||||
public static RouteDescriptorCollection DefaultCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DefaultCollection == null)
|
||||
_DefaultCollection = new RouteDescriptorCollection();
|
||||
return _DefaultCollection;
|
||||
}
|
||||
}
|
||||
private static RouteDescriptorCollection _DefaultCollection;
|
||||
|
||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
||||
/// destination exists already, it will not be added - in order to preserve
|
||||
/// proper route releasing.
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
public void AddRouteDescriptor(RouteDescriptor descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination)
|
||||
&& RouteDescriptors.Any(t => t.Destination == descriptor.Destination && t.InputPort != null && descriptor.InputPort != null && t.InputPort.Key == descriptor.InputPort.Key))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
||||
"Route to [{0}] already exists in global routes table", descriptor?.Source?.Key);
|
||||
return;
|
||||
}
|
||||
RouteDescriptors.Add(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RouteDescriptor for a destination
|
||||
/// </summary>
|
||||
/// <returns>null if no RouteDescriptor for a destination exists</returns>
|
||||
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs destination)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}'", destination?.Key ?? null);
|
||||
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||
}
|
||||
|
||||
public RouteDescriptor GetRouteDescriptorForDestinationAndInputPort(IRoutingInputs destination, string inputPortKey)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Getting route descriptor for '{destination}':'{inputPortKey}'", destination?.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination && rd.InputPort != null && rd.InputPort.Key == inputPortKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
|
||||
/// Returns null if no route with the provided destination exists.
|
||||
/// </summary>
|
||||
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs destination, string inputPortKey = "")
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Removing route descriptor for '{destination}':'{inputPortKey}'", destination.Key ?? null, string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
|
||||
var descr = string.IsNullOrEmpty(inputPortKey)
|
||||
? GetRouteDescriptorForDestination(destination)
|
||||
: GetRouteDescriptorForDestinationAndInputPort(destination, inputPortKey);
|
||||
if (descr != null)
|
||||
RouteDescriptors.Remove(descr);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Found route descriptor {routeDescriptor}", destination, descr);
|
||||
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// A collection of RouteDescriptors - typically the static DefaultCollection is used
|
||||
/// </summary>
|
||||
public class RouteDescriptorCollection<TInputSelector, TOutputSelector>
|
||||
{
|
||||
public static RouteDescriptorCollection<TInputSelector, TOutputSelector> DefaultCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_DefaultCollection == null)
|
||||
_DefaultCollection = new RouteDescriptorCollection<TInputSelector, TOutputSelector>();
|
||||
return _DefaultCollection;
|
||||
}
|
||||
}
|
||||
private static RouteDescriptorCollection<TInputSelector, TOutputSelector> _DefaultCollection;
|
||||
|
||||
private readonly List<RouteDescriptor> RouteDescriptors = new List<RouteDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a RouteDescriptor to the list. If an existing RouteDescriptor for the
|
||||
/// destination exists already, it will not be added - in order to preserve
|
||||
/// proper route releasing.
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
public void AddRouteDescriptor(RouteDescriptor descriptor)
|
||||
{
|
||||
if (RouteDescriptors.Any(t => t.Destination == descriptor.Destination))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, descriptor.Destination,
|
||||
"Route to [{0}] already exists in global routes table", descriptor.Source.Key);
|
||||
return;
|
||||
}
|
||||
RouteDescriptors.Add(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RouteDescriptor for a destination
|
||||
/// </summary>
|
||||
/// <returns>null if no RouteDescriptor for a destination exists</returns>
|
||||
public RouteDescriptor GetRouteDescriptorForDestination(IRoutingInputs<TInputSelector> destination)
|
||||
{
|
||||
return RouteDescriptors.FirstOrDefault(rd => rd.Destination == destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RouteDescriptor for a given destination AND removes it from collection.
|
||||
/// Returns null if no route with the provided destination exists.
|
||||
/// </summary>
|
||||
public RouteDescriptor RemoveRouteDescriptor(IRoutingInputs<TInputSelector> destination)
|
||||
{
|
||||
var descr = GetRouteDescriptorForDestination(destination);
|
||||
if (descr != null)
|
||||
RouteDescriptors.Remove(descr);
|
||||
return descr;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
47
src/PepperDash.Essentials.Core/Routing/RouteRequest.cs
Normal file
47
src/PepperDash.Essentials.Core/Routing/RouteRequest.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class RouteRequest
|
||||
{
|
||||
public RoutingInputPort DestinationPort { get; set; }
|
||||
|
||||
public RoutingOutputPort SourcePort { get; set; }
|
||||
public IRoutingInputs Destination { get; set; }
|
||||
public IRoutingOutputs Source { get; set; }
|
||||
public eRoutingSignalType SignalType { get; set; }
|
||||
|
||||
public void HandleCooldown(object sender, FeedbackEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Handling cooldown route request: {destination}:{destinationPort} -> {source}:{sourcePort} {type}", null, Destination?.Key ?? "empty destination", DestinationPort?.Key ?? "no destination port", Source?.Key ?? "empty source", SourcePort?.Key ?? "empty source port", SignalType.ToString());
|
||||
|
||||
if (args.BoolValue == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "Cooldown complete. Making route from {destination} to {source}", Destination?.Key, Source?.Key);
|
||||
|
||||
Destination.ReleaseAndMakeRoute(Source, SignalType, DestinationPort?.Key ?? string.Empty, SourcePort?.Key ?? string.Empty);
|
||||
|
||||
if (sender is IWarmingCooling coolingDevice)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "Unsubscribing from cooling feedback for {destination}", null, Destination.Key);
|
||||
coolingDevice.IsCoolingDownFeedback.OutputChange -= HandleCooldown;
|
||||
}
|
||||
} catch(Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Exception handling cooldown", Destination);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Queues;
|
||||
using System;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
public class RouteRequestQueueItem : IQueueMessage
|
||||
{
|
||||
private readonly Action<RouteRequest> action;
|
||||
private readonly RouteRequest routeRequest;
|
||||
|
||||
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
|
||||
{
|
||||
action = routeAction;
|
||||
routeRequest = request;
|
||||
}
|
||||
|
||||
public void Dispatch()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
|
||||
action(routeRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReleaseRouteQueueItem : IQueueMessage
|
||||
{
|
||||
private readonly Action<IRoutingInputs, string> action;
|
||||
private readonly IRoutingInputs destination;
|
||||
private readonly string inputPortKey;
|
||||
|
||||
public ReleaseRouteQueueItem(Action<IRoutingInputs, string> action, IRoutingInputs destination, string inputPortKey)
|
||||
{
|
||||
this.action = action;
|
||||
this.destination = destination;
|
||||
this.inputPortKey = inputPortKey;
|
||||
}
|
||||
|
||||
public void Dispatch()
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||
action(destination, inputPortKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an individual link for a route
|
||||
/// </summary>
|
||||
public class RouteSwitchDescriptor
|
||||
{
|
||||
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
|
||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
}
|
||||
|
||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
OutputPort = outputPort;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (SwitchingDevice is IRouting)
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches output {(OutputPort != null ? OutputPort.Key : "No output port")} to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
else
|
||||
return $"{(SwitchingDevice != null ? SwitchingDevice.Key : "No Device")} switches to input {(InputPort != null ? InputPort.Key : "No input port")}";
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// Represents an individual link for a route
|
||||
/// </summary>
|
||||
public class RouteSwitchDescriptor<TInputSelector, TOutputSelector>
|
||||
{
|
||||
public IRoutingInputs<TInputSelector> SwitchingDevice { get { return InputPort.ParentDevice; } }
|
||||
public RoutingOutputPort<TOutputSelector> OutputPort { get; set; }
|
||||
public RoutingInputPort<TInputSelector> InputPort { get; set; }
|
||||
|
||||
public RouteSwitchDescriptor(RoutingInputPort<TInputSelector> inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
}
|
||||
|
||||
public RouteSwitchDescriptor(RoutingOutputPort<TOutputSelector> outputPort, RoutingInputPort<TInputSelector> inputPort)
|
||||
{
|
||||
InputPort = inputPort;
|
||||
OutputPort = outputPort;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (SwitchingDevice is IRouting)
|
||||
return string.Format("{0} switches output '{1}' to input '{2}'", SwitchingDevice.Key, OutputPort.Selector, InputPort.Selector);
|
||||
else
|
||||
return string.Format("{0} switches to input '{1}'", SwitchingDevice.Key, InputPort.Selector);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
271
src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
Normal file
271
src/PepperDash.Essentials.Core/Routing/RoutingFeedbackManager.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
{
|
||||
public class RoutingFeedbackManager:EssentialsDevice
|
||||
{
|
||||
public RoutingFeedbackManager(string key, string name): base(key, name)
|
||||
{
|
||||
AddPreActivationAction(SubscribeForMidpointFeedback);
|
||||
AddPreActivationAction(SubscribeForSinkFeedback);
|
||||
}
|
||||
|
||||
|
||||
private void SubscribeForMidpointFeedback()
|
||||
{
|
||||
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
|
||||
|
||||
foreach (var device in midpointDevices)
|
||||
{
|
||||
device.RouteChanged += HandleMidpointUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeForSinkFeedback()
|
||||
{
|
||||
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
|
||||
foreach (var device in sinkDevices)
|
||||
{
|
||||
device.InputChanged += HandleSinkUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
|
||||
{
|
||||
try
|
||||
{
|
||||
var devices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
UpdateDestination(device, device.CurrentInputPort);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error handling midpoint update from {midpointKey}:{Exception}", this, midpoint.Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateDestination(sender, currentInputPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error handling Sink update from {senderKey}:{Exception}", this, sender.Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
|
||||
|
||||
if(inputPort == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "Destination {destination} has not reported an input port yet", this,destination.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
TieLine firstTieLine;
|
||||
try
|
||||
{
|
||||
var tieLines = TieLineCollection.Default;
|
||||
|
||||
firstTieLine = tieLines.FirstOrDefault(tl => tl.DestinationPort.Key == inputPort.Key && tl.DestinationPort.ParentDevice.Key == inputPort.ParentDevice.Key);
|
||||
|
||||
if (firstTieLine == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No tieline found for inputPort {inputPort}. Clearing current source", this, inputPort);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
SourceKey = "$transient",
|
||||
Name = inputPort.Key,
|
||||
};
|
||||
|
||||
|
||||
destination.CurrentSourceInfo = tempSourceListItem; ;
|
||||
destination.CurrentSourceInfoKey = "$transient";
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error getting first tieline: {Exception}", this, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Getting source for first TieLine {tieLine}", this, firstTieLine);
|
||||
|
||||
TieLine sourceTieLine;
|
||||
try
|
||||
{
|
||||
sourceTieLine = GetRootTieLine(firstTieLine);
|
||||
|
||||
if (sourceTieLine == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route found to source for inputPort {inputPort}. Clearing current source", this, inputPort);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
SourceKey = "$transient",
|
||||
Name = "None",
|
||||
};
|
||||
|
||||
destination.CurrentSourceInfo = tempSourceListItem;
|
||||
destination.CurrentSourceInfoKey = string.Empty;
|
||||
return;
|
||||
}
|
||||
} catch(Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error getting sourceTieLine: {Exception}", this, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root TieLine {tieLine}", this, sourceTieLine);
|
||||
|
||||
// Does not handle combinable scenarios or other scenarios where a display might be part of multiple rooms yet.
|
||||
var room = DeviceManager.AllDevices.OfType<IEssentialsRoom>().FirstOrDefault((r) => {
|
||||
if(r is IHasMultipleDisplays roomMultipleDisplays)
|
||||
{
|
||||
return roomMultipleDisplays.Displays.Any(d => d.Value.Key == destination.Key);
|
||||
}
|
||||
|
||||
if(r is IHasDefaultDisplay roomDefaultDisplay)
|
||||
{
|
||||
return roomDefaultDisplay.DefaultDisplay.Key == destination.Key;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if(room == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No room found for display {destination}", this, destination.Key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found room {room} for destination {destination}", this, room.Key, destination.Key);
|
||||
|
||||
var sourceList = ConfigReader.ConfigObject.GetSourceListForKey(room.SourceListKey);
|
||||
|
||||
if (sourceList == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "No source list found for source list key {key}. Unable to find source for tieLine {sourceTieLine}", this, room.SourceListKey, sourceTieLine);
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found sourceList for room {room}", this, room.Key);
|
||||
|
||||
var sourceListItem = sourceList.FirstOrDefault(sli => {
|
||||
//// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose,
|
||||
// "SourceListItem {sourceListItem}:{sourceKey} tieLine sourceport device key {sourcePortDeviceKey}",
|
||||
// this,
|
||||
// sli.Key,
|
||||
// sli.Value.SourceKey,
|
||||
// sourceTieLine.SourcePort.ParentDevice.Key);
|
||||
|
||||
return sli.Value.SourceKey.Equals(sourceTieLine.SourcePort.ParentDevice.Key,StringComparison.InvariantCultureIgnoreCase);
|
||||
});
|
||||
|
||||
var source = sourceListItem.Value;
|
||||
var sourceKey = sourceListItem.Key;
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No source found for device {key}. Creating transient source for {destination}", this, sourceTieLine.SourcePort.ParentDevice.Key, destination);
|
||||
|
||||
var tempSourceListItem = new SourceListItem
|
||||
{
|
||||
SourceKey = "$transient",
|
||||
Name = sourceTieLine.SourcePort.Key,
|
||||
};
|
||||
|
||||
destination.CurrentSourceInfoKey = "$transient";
|
||||
destination.CurrentSourceInfo = tempSourceListItem;
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Got Source {@source} with key {sourceKey}", this, source, sourceKey);
|
||||
|
||||
destination.CurrentSourceInfoKey = sourceKey;
|
||||
destination.CurrentSourceInfo = source;
|
||||
|
||||
}
|
||||
|
||||
private TieLine GetRootTieLine(TieLine tieLine)
|
||||
{
|
||||
TieLine nextTieLine = null;
|
||||
try
|
||||
{
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "**Following tieLine {tieLine}**", this, tieLine);
|
||||
|
||||
if (tieLine.SourcePort.ParentDevice is IRoutingWithFeedback midpoint)
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source device {sourceDevice} is midpoint", this, midpoint);
|
||||
|
||||
if(midpoint.CurrentRoutes == null || midpoint.CurrentRoutes.Count == 0)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "Midpoint {midpointKey} has no routes",this, midpoint.Key);
|
||||
return null;
|
||||
}
|
||||
|
||||
var currentRoute = midpoint.CurrentRoutes.FirstOrDefault(route => {
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", this, route, tieLine);
|
||||
|
||||
return route.OutputPort != null && route.InputPort != null && route.OutputPort?.Key == tieLine.SourcePort.Key && route.OutputPort?.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key;
|
||||
});
|
||||
|
||||
if (currentRoute == null)
|
||||
{
|
||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Information, "No route through midpoint {midpoint} for outputPort {outputPort}", this, midpoint.Key, tieLine.SourcePort);
|
||||
return null;
|
||||
}
|
||||
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found currentRoute {currentRoute} through {midpoint}", this, currentRoute, midpoint);
|
||||
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => {
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Checking {route} against {tieLine}", tl.DestinationPort.Key, currentRoute.InputPort.Key);
|
||||
return tl.DestinationPort.Key == currentRoute.InputPort.Key && tl.DestinationPort.ParentDevice.Key == currentRoute.InputPort.ParentDevice.Key; });
|
||||
|
||||
if (nextTieLine != null)
|
||||
{
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found next tieLine {tieLine}. Walking the chain", this, nextTieLine);
|
||||
return GetRootTieLine(nextTieLine);
|
||||
}
|
||||
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root tieLine {tieLine}", this,nextTieLine);
|
||||
return nextTieLine;
|
||||
}
|
||||
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLIne Source Device {sourceDeviceKey} is IRoutingSource: {isIRoutingSource}", this, tieLine.SourcePort.ParentDevice.Key, tieLine.SourcePort.ParentDevice is IRoutingSource);
|
||||
//Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "TieLine Source Device interfaces: {typeFullName}:{interfaces}", this, tieLine.SourcePort.ParentDevice.GetType().FullName, tieLine.SourcePort.ParentDevice.GetType().GetInterfaces().Select(i => i.Name));
|
||||
|
||||
if (tieLine.SourcePort.ParentDevice is IRoutingSource || tieLine.SourcePort.ParentDevice is IRoutingOutputs) //end of the chain
|
||||
{
|
||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Found root: {tieLine}", this, tieLine);
|
||||
return tieLine;
|
||||
}
|
||||
|
||||
nextTieLine = TieLineCollection.Default.FirstOrDefault(tl => tl.DestinationPort.Key == tieLine.SourcePort.Key && tl.DestinationPort.ParentDevice.Key == tieLine.SourcePort.ParentDevice.Key );
|
||||
|
||||
if (nextTieLine != null)
|
||||
{
|
||||
return GetRootTieLine(nextTieLine);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(ex, "Error walking tieLines: {Exception}", this, ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs
Normal file
82
src/PepperDash.Essentials.Core/Routing/RoutingInputPort.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic RoutingInput with no statuses.
|
||||
/// </summary>
|
||||
public class RoutingInputPort : RoutingPort
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingInputs object this lives on
|
||||
/// </summary>
|
||||
public IRoutingInputs ParentDevice { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a basic RoutingInputPort
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingInputs parent)
|
||||
: this (key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a virtual routing input port that lives inside a device. For example
|
||||
/// the ports that link a DM card to a DM matrix bus
|
||||
/// </summary>
|
||||
/// <param name="isInternal">true for internal ports</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingInputs parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException(nameof(parent));
|
||||
ParentDevice = parent;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// Basic RoutingInput with no statuses.
|
||||
/// </summary>
|
||||
public class RoutingInputPort<TSelector> : RoutingPort<TSelector>
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingInputs object this lives on
|
||||
/// </summary>
|
||||
public IRoutingInputs<TSelector> ParentDevice { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a basic RoutingInputPort
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
TSelector selector, IRoutingInputs<TSelector> parent)
|
||||
: this(key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a virtual routing input port that lives inside a device. For example
|
||||
/// the ports that link a DM card to a DM matrix bus
|
||||
/// </summary>
|
||||
/// <param name="isInternal">true for internal ports</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
TSelector selector, IRoutingInputs<TSelector> parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A RoutingInputPort for devices like DM-TX and DM input cards.
|
||||
/// Will provide video statistics on connected signals
|
||||
/// </summary>
|
||||
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
|
||||
{
|
||||
/// <summary>
|
||||
/// Video statuses attached to this port
|
||||
/// </summary>
|
||||
public VideoStatusOutputs VideoStatus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
|
||||
/// the values for the various stats</param>
|
||||
public RoutingInputPortWithVideoStatuses(string key,
|
||||
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
|
||||
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
|
||||
base(key, type, connType, selector, parent)
|
||||
{
|
||||
VideoStatus = new VideoStatusOutputs(funcs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The handler type for a Room's SourceInfoChange
|
||||
/// </summary>
|
||||
public delegate void SourceInfoChangeHandler(/*EssentialsRoomBase room,*/ SourceListItem info, ChangeType type);
|
||||
|
||||
|
||||
//*******************************************************************************************
|
||||
// Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// For rooms with a single presentation source, change event
|
||||
/// </summary>
|
||||
public interface IHasCurrentSourceInfoChange
|
||||
{
|
||||
string CurrentSourceInfoKey { get; set; }
|
||||
SourceListItem CurrentSourceInfo { get; set; }
|
||||
event SourceInfoChangeHandler CurrentSourceChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a class that has a collection of RoutingInputPorts
|
||||
/// </summary>
|
||||
public interface IRoutingInputs : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingInputPort> InputPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a class that has a collection of RoutingOutputPorts
|
||||
/// </summary>
|
||||
|
||||
public interface IRoutingOutputs : IKeyed
|
||||
{
|
||||
RoutingPortCollection<RoutingOutputPort> OutputPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For fixed-source endpoint devices
|
||||
/// </summary>
|
||||
public interface IRoutingSink : IRoutingInputs, IHasCurrentSourceInfoChange
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint device like a display, that selects inputs
|
||||
/// </summary>
|
||||
public interface IRoutingSinkWithSwitching : IRoutingSink
|
||||
{
|
||||
//void ClearRoute();
|
||||
void ExecuteSwitch(object inputSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For devices like RMCs, baluns, other devices with no switching.
|
||||
/// </summary>
|
||||
public interface IRoutingInputsOutputs : IRoutingInputs, IRoutingOutputs
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a midpoint device as have internal routing. Any devices in the middle of the
|
||||
/// signal chain, that do switching, must implement this for routing to work otherwise
|
||||
/// the routing algorithm will treat the IRoutingInputsOutputs device as a passthrough
|
||||
/// device.
|
||||
/// </summary>
|
||||
public interface IRouting : IRoutingInputsOutputs
|
||||
{
|
||||
void ExecuteSwitch(object inputSelector, object outputSelector, eRoutingSignalType signalType);
|
||||
}
|
||||
|
||||
public interface IRoutingWithClear : IRouting
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears a route to an output, however a device needs to do that
|
||||
/// </summary>
|
||||
/// <param name="outputSelector">Output to clear</param>
|
||||
/// <param name="signalType">signal type to clear</param>
|
||||
void ClearRoute(object outputSelector, eRoutingSignalType signalType);
|
||||
}
|
||||
|
||||
public interface IRoutingNumeric : IRouting
|
||||
{
|
||||
void ExecuteNumericSwitch(ushort input, ushort output, eRoutingSignalType type);
|
||||
}
|
||||
|
||||
public interface ITxRouting : IRoutingNumeric
|
||||
{
|
||||
IntFeedback VideoSourceNumericFeedback { get; }
|
||||
IntFeedback AudioSourceNumericFeedback { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a receiver that has internal routing (DM-RMC-4K-Z-SCALER-C)
|
||||
/// </summary>
|
||||
public interface IRmcRouting : IRoutingNumeric
|
||||
{
|
||||
IntFeedback AudioVideoSourceNumericFeedback { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines an IRmcRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface ITxRoutingWithFeedback : ITxRouting
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an IRmcRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface IRmcRoutingWithFeedback : IRmcRouting
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
|
||||
/// </summary>
|
||||
public interface IRoutingSource : IRoutingOutputs
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an event structure for reporting output route data
|
||||
/// </summary>
|
||||
public interface IRoutingFeedback : IKeyName
|
||||
{
|
||||
event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
|
||||
//void OnSwitchChange(RoutingNumericEventArgs e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an IRoutingNumeric with a feedback event
|
||||
/// </summary>
|
||||
public interface IRoutingNumericWithFeedback : IRoutingNumeric, IRoutingFeedback
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an IRouting with a feedback event
|
||||
/// </summary>
|
||||
public interface IRoutingWithFeedback : IRouting, IRoutingFeedback
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class RoutingNumericEventArgs : EventArgs
|
||||
{
|
||||
|
||||
public uint? Output { get; set; }
|
||||
public uint? Input { get; set; }
|
||||
|
||||
public eRoutingSignalType SigType { get; set; }
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
|
||||
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
|
||||
eRoutingSignalType sigType)
|
||||
: this(null, null, outputPort, inputPort, sigType)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs()
|
||||
: this(null, null, null, null, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
|
||||
RoutingInputPort inputPort, eRoutingSignalType sigType)
|
||||
{
|
||||
OutputPort = outputPort;
|
||||
InputPort = inputPort;
|
||||
|
||||
Output = output;
|
||||
Input = input;
|
||||
SigType = sigType;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IRoutingHasVideoInputSyncFeedbacks
|
||||
{
|
||||
FeedbackCollection<BoolFeedback> VideoInputSyncFeedbacks { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class RoutingNumericEventArgs : EventArgs
|
||||
{
|
||||
|
||||
public uint? Output { get; set; }
|
||||
public uint? Input { get; set; }
|
||||
|
||||
public eRoutingSignalType SigType { get; set; }
|
||||
public RoutingInputPort InputPort { get; set; }
|
||||
public RoutingOutputPort OutputPort { get; set; }
|
||||
|
||||
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
|
||||
eRoutingSignalType sigType)
|
||||
: this(null, null, outputPort, inputPort, sigType)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs()
|
||||
: this(null, null, null, null, 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
|
||||
RoutingInputPort inputPort, eRoutingSignalType sigType)
|
||||
{
|
||||
OutputPort = outputPort;
|
||||
InputPort = inputPort;
|
||||
|
||||
Output = output;
|
||||
Input = input;
|
||||
SigType = sigType;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs
Normal file
75
src/PepperDash.Essentials.Core/Routing/RoutingOutputPort.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
public class RoutingOutputPort : RoutingPort
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingOutputs object this port lives on
|
||||
/// </summary>
|
||||
public IRoutingOutputs ParentDevice { get; private set; }
|
||||
|
||||
public InUseTracking InUseTracker { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingOutputs parent)
|
||||
: this(key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingOutputs parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
InUseTracker = new InUseTracking();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
||||
}
|
||||
}
|
||||
|
||||
/*public class RoutingOutputPort<TSelector> : RoutingPort<TSelector>
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingOutputs object this port lives on
|
||||
/// </summary>
|
||||
public IRoutingOutputs ParentDevice { get; private set; }
|
||||
|
||||
public InUseTracking InUseTracker { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
TSelector selector, IRoutingOutputs parent)
|
||||
: this(key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
TSelector selector, IRoutingOutputs parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
ParentDevice = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
InUseTracker = new InUseTracking();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ParentDevice.Key + ":" + Key;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for RoutingInput and Output ports
|
||||
/// </summary>
|
||||
public abstract class RoutingPort : IKeyed
|
||||
/// <summary>
|
||||
/// Base class for RoutingInput and Output ports
|
||||
/// </summary>
|
||||
public abstract class RoutingPort : IKeyed
|
||||
{
|
||||
public string Key { get; private set; }
|
||||
public eRoutingSignalType Type { get; private set; }
|
||||
@@ -26,183 +23,26 @@ namespace PepperDash.Essentials.Core
|
||||
ConnectionType = connType;
|
||||
Selector = selector;
|
||||
IsInternal = isInternal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum eRoutingSignalType
|
||||
{
|
||||
Audio = 1,
|
||||
Video = 2,
|
||||
AudioVideo = Audio | Video,
|
||||
UsbOutput = 8,
|
||||
UsbInput = 16,
|
||||
SecondaryAudio = 32
|
||||
}
|
||||
/*public abstract class RoutingPort<TSelector>:IKeyed
|
||||
{
|
||||
public string Key { get; private set; }
|
||||
public eRoutingSignalType Type { get; private set; }
|
||||
public eRoutingPortConnectionType ConnectionType { get; private set; }
|
||||
public readonly TSelector Selector;
|
||||
public bool IsInternal { get; private set; }
|
||||
public object FeedbackMatchObject { get; set; }
|
||||
public object Port { get; set; }
|
||||
|
||||
public enum eRoutingPortConnectionType
|
||||
{
|
||||
None, BackplaneOnly, DisplayPort, Dvi, Hdmi, Rgb, Vga, LineAudio, DigitalAudio, Sdi,
|
||||
Composite, Component, DmCat, DmMmFiber, DmSmFiber, Speaker, Streaming, UsbC, HdBaseT
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic RoutingInput with no statuses.
|
||||
/// </summary>
|
||||
public class RoutingInputPort : RoutingPort
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingInputs object this lives on
|
||||
/// </summary>
|
||||
public IRoutingInputs ParentDevice { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a basic RoutingInputPort
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingInputs parent)
|
||||
: this (key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a virtual routing input port that lives inside a device. For example
|
||||
/// the ports that link a DM card to a DM matrix bus
|
||||
/// </summary>
|
||||
/// <param name="isInternal">true for internal ports</param>
|
||||
public RoutingInputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingInputs parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException("parent");
|
||||
ParentDevice = parent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// Static method to get a named port from a named device
|
||||
///// </summary>
|
||||
///// <returns>Returns null if device or port doesn't exist</returns>
|
||||
//public static RoutingInputPort GetDevicePort(string deviceKey, string portKey)
|
||||
//{
|
||||
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingInputs;
|
||||
// if (sourceDev == null)
|
||||
// return null;
|
||||
// return sourceDev.InputPorts[portKey];
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Static method to get a named port from a card in a named ICardPortsDevice device
|
||||
///// Uses ICardPortsDevice.GetChildInputPort
|
||||
///// </summary>
|
||||
///// <param name="cardKey">'input-N'</param>
|
||||
///// <returns>null if device, card or port doesn't exist</returns>
|
||||
//public static RoutingInputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey)
|
||||
//{
|
||||
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice;
|
||||
// if (sourceDev == null)
|
||||
// return null;
|
||||
// return sourceDev.GetChildInputPort(cardKey, portKey);
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A RoutingInputPort for devices like DM-TX and DM input cards.
|
||||
/// Will provide video statistics on connected signals
|
||||
/// </summary>
|
||||
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
|
||||
{
|
||||
/// <summary>
|
||||
/// Video statuses attached to this port
|
||||
/// </summary>
|
||||
public VideoStatusOutputs VideoStatus { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
|
||||
/// the values for the various stats</param>
|
||||
public RoutingInputPortWithVideoStatuses(string key,
|
||||
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
|
||||
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
|
||||
base(key, type, connType, selector, parent)
|
||||
{
|
||||
VideoStatus = new VideoStatusOutputs(funcs);
|
||||
}
|
||||
}
|
||||
|
||||
public class RoutingOutputPort : RoutingPort
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRoutingOutputs object this port lives on
|
||||
/// </summary>
|
||||
public IRoutingOutputs ParentDevice { get; private set; }
|
||||
|
||||
public InUseTracking InUseTracker { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||
/// May be string, number, whatever</param>
|
||||
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingOutputs parent)
|
||||
: this(key, type, connType, selector, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||
object selector, IRoutingOutputs parent, bool isInternal)
|
||||
: base(key, type, connType, selector, isInternal)
|
||||
{
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException("parent");
|
||||
ParentDevice = parent;
|
||||
InUseTracker = new InUseTracking();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ParentDevice.Key + ":" + Key;
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// Static method to get a named port from a named device
|
||||
///// </summary>
|
||||
///// <returns>Returns null if device or port doesn't exist</returns>
|
||||
//public static RoutingOutputPort GetDevicePort(string deviceKey, string portKey)
|
||||
//{
|
||||
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as IRoutingOutputs;
|
||||
// if (sourceDev == null)
|
||||
// return null;
|
||||
// var port = sourceDev.OutputPorts[portKey];
|
||||
// if (port == null)
|
||||
// Debug.LogMessage(LogEventLevel.Information, "WARNING: Device '{0}' does does not contain output port '{1}'", deviceKey, portKey);
|
||||
// return port;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Static method to get a named port from a card in a named ICardPortsDevice device
|
||||
///// Uses ICardPortsDevice.GetChildOutputPort on that device
|
||||
///// </summary>
|
||||
///// <param name="cardKey">'input-N' or 'output-N'</param>
|
||||
///// <returns>null if device, card or port doesn't exist</returns>
|
||||
//public static RoutingOutputPort GetDeviceCardPort(string deviceKey, string cardKey, string portKey)
|
||||
//{
|
||||
// var sourceDev = DeviceManager.GetDeviceForKey(deviceKey) as ICardPortsDevice;
|
||||
// if (sourceDev == null)
|
||||
// return null;
|
||||
// var port = sourceDev.GetChildOutputPort(cardKey, portKey);
|
||||
//}
|
||||
}
|
||||
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, TSelector selector, bool isInternal)
|
||||
{
|
||||
Key = key;
|
||||
Type = type;
|
||||
ConnectionType = connType;
|
||||
Selector = selector;
|
||||
IsInternal = isInternal;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -23,4 +23,21 @@ namespace PepperDash.Essentials.Core
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// Basically a List , with an indexer to find ports by key name
|
||||
/// </summary>
|
||||
public class RoutingPortCollection<T, TSelector> : List<T> where T : RoutingPort<TSelector>
|
||||
{
|
||||
/// <summary>
|
||||
/// Case-insensitive port lookup linked to ports' keys
|
||||
/// </summary>
|
||||
public T this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.FirstOrDefault(i => i.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Routing
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// These should correspond directly with the portNames var in the config tool.
|
||||
@@ -239,5 +239,13 @@ namespace PepperDash.Essentials.Core.Routing
|
||||
/// HdBaseTOut
|
||||
/// </summary>
|
||||
public const string HdBaseTOut = "hdBaseTOut";
|
||||
/// <summary>
|
||||
/// SdiIn
|
||||
/// </summary>
|
||||
public const string SdiIn = "sdiIn";
|
||||
/// <summary>
|
||||
/// SdiOut
|
||||
/// </summary>
|
||||
public const string SdiOut = "sdiOut";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user